Continued From wrangle_and_split.Rmd

See the upstream wrangle and split notebook here.

See the project overview in the README.

Diving In

Back to top.

Libraries and Source Files

library(tidyverse)
# library(dict) # Still not found after installation
library(container) # For Dict class
library(useful) # For simple.impute
library(comprehenr) # For list comprehension
library(GGally)
library(reshape2)
library(gridExtra)
library(gplots)
library(DescTools) # For df summary
library(robustHD) # For df summary
library(caret)
library(effsize) # For Cohen's d

source('tools/wrangle.R')
source('tools/eda.R')
source('tools/engineer.R')
source('tools/split.R')

# SEED = 65466

Loading Data

Here’s the wrangled training set loaded from objects serialized at the end of wrangle_and_split.Rmd.

val_train_X = readRDS("data/val_train_X_wrangled.rds")
val_train_y = readRDS("data/val_train_y_wrangled.rds")

# Merge for easier analysis.
val_train_Xy = merge(
  x = val_train_X,
  y = val_train_y,
  by = 'Id',
  all = TRUE
)

EDA and Engineering

Back to top.

str(val_train_Xy)
'data.frame':   715 obs. of  81 variables:
 $ Id           : chr  "1" "10" "1000" "1002" ...
 $ MSSubClass   : Factor w/ 16 levels "20","30","40",..: 6 16 1 2 1 9 14 1 5 11 ...
 $ MSZoning     : Factor w/ 8 levels "A","C","FV","I",..: 6 6 6 6 6 6 8 6 6 6 ...
 $ LotFrontage  : num  65 50 64 60 75 65 21 NA 115 75 ...
 $ LotArea      : num  8450 7420 6762 5400 11957 ...
 $ Street       : Ord.factor w/ 3 levels "None"<"Grvl"<..: 3 3 3 3 3 3 3 3 3 3 ...
 $ Alley        : Ord.factor w/ 3 levels "None"<"Grvl"<..: 1 1 1 1 1 1 1 1 1 1 ...
 $ LotShape     : Ord.factor w/ 4 levels "Reg"<"IR1"<"IR2"<..: 1 1 1 1 2 1 1 2 1 1 ...
 $ LandContour  : Factor w/ 4 levels "Lvl","Bnk","HLS",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Utilities    : Ord.factor w/ 5 levels "None"<"ELO"<"NoSeWa"<..: 5 5 5 5 5 5 5 5 5 5 ...
 $ LotConfig    : Factor w/ 5 levels "Inside","Corner",..: 1 2 1 2 1 1 1 1 1 1 ...
 $ LandSlope    : Ord.factor w/ 4 levels "None"<"Gtl"<"Mod"<..: 2 2 2 2 2 2 2 2 2 2 ...
 $ Neighborhood : Factor w/ 25 levels "Blmngtn","Blueste",..: 6 4 6 18 22 6 11 17 20 8 ...
 $ Condition1   : Factor w/ 9 levels "Artery","Feedr",..: 3 1 3 3 5 3 3 3 3 3 ...
 $ Condition2   : Factor w/ 9 levels "Artery","Feedr",..: 3 1 3 3 3 3 3 3 3 3 ...
 $ BldgType     : Factor w/ 5 levels "1Fam","2FmCon",..: 1 2 1 1 1 1 4 1 1 3 ...
 $ HouseStyle   : Factor w/ 8 levels "1Story","1.5Fin",..: 4 3 1 1 1 8 4 1 2 1 ...
 $ OverallQual  : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 7 5 7 5 8 5 4 6 5 5 ...
 $ OverallCond  : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 5 6 5 6 5 8 4 7 5 5 ...
 $ YearBuilt    : int  2003 1939 2006 1920 2006 1977 1970 1977 1948 1965 ...
 $ YearRemodAdd : int  2003 1950 2006 1950 2006 1977 1970 2001 1950 1965 ...
 $ RoofStyle    : Factor w/ 6 levels "Flat","Gable",..: 2 2 2 2 2 2 2 2 2 4 ...
 $ RoofMatl     : Factor w/ 8 levels "ClyTile","CompShg",..: 2 2 2 2 2 2 2 2 2 2 ...
 $ Exterior1st  : Factor w/ 17 levels "AsbShng","AsphShn",..: 15 9 15 16 15 7 6 11 16 2 ...
 $ Exterior2nd  : Factor w/ 18 levels "AsbShng","AsphShn",..: 15 9 15 16 15 7 6 11 16 2 ...
 $ MasVnrType   : Factor w/ 5 levels "BrkCmn","BrkFace",..: 2 4 5 4 2 2 4 2 4 4 ...
 $ MasVnrArea   : num  196 0 24 0 53 220 0 28 0 0 ...
 $ ExterQual    : Ord.factor w/ 5 levels "Po"<"Fa"<"TA"<..: 4 3 4 3 4 4 3 3 3 3 ...
 $ ExterCond    : Ord.factor w/ 5 levels "Po"<"Fa"<"TA"<..: 3 3 3 3 3 3 3 3 3 3 ...
 $ Foundation   : Factor w/ 6 levels "BrkTil","CBlock",..: 3 1 3 1 3 2 2 3 2 2 ...
 $ BsmtQual     : Ord.factor w/ 6 levels "None"<"Po"<"Fa"<..: 5 4 5 3 5 5 4 4 4 1 ...
 $ BsmtCond     : Ord.factor w/ 6 levels "None"<"Po"<"Fa"<..: 4 4 4 4 4 5 4 4 4 1 ...
 $ BsmtExposure : Ord.factor w/ 5 levels "None"<"No"<"Mn"<..: 2 2 4 2 2 4 2 3 2 1 ...
 $ BsmtFinType1 : Ord.factor w/ 7 levels "None"<"Unf"<"LwQ"<..: 7 7 7 2 7 7 5 6 2 1 ...
 $ BsmtFinSF1   : num  706 851 686 0 24 595 273 1200 0 0 ...
 $ BsmtFinType2 : Ord.factor w/ 7 levels "None"<"Unf"<"LwQ"<..: 2 2 2 2 2 2 3 2 2 1 ...
 $ BsmtFinSF2   : num  0 0 0 0 0 0 273 0 0 0 ...
 $ BsmtUnfSF    : num  150 140 501 691 1550 390 0 410 720 0 ...
 $ TotalBsmtSF  : num  856 991 1187 691 1574 ...
 $ Heating      : Factor w/ 7 levels "None","Floor",..: 3 3 3 3 3 3 3 3 3 3 ...
 $ HeatingQC    : Ord.factor w/ 6 levels "None"<"Po"<"Fa"<..: 6 6 6 6 6 4 4 5 4 4 ...
 $ CentralAir   : Ord.factor w/ 2 levels "N"<"Y": 2 2 2 2 2 2 2 2 2 1 ...
 $ Electrical   : Factor w/ 6 levels "None","SBrkr",..: 2 2 2 3 2 2 2 2 2 2 ...
 $ X1stFlrSF    : num  856 1077 1208 691 1574 ...
 $ X2ndFlrSF    : num  854 0 0 0 0 0 546 0 551 0 ...
 $ LowQualFinSF : num  0 0 0 0 0 0 0 0 0 0 ...
 $ GrLivArea    : num  1710 1077 1208 691 1574 ...
 $ BsmtFullBath : int  1 1 1 0 0 0 0 1 0 0 ...
 $ BsmtHalfBath : int  0 0 0 0 0 0 0 0 0 0 ...
 $ FullBath     : int  2 1 2 1 2 2 1 2 2 2 ...
 $ HalfBath     : int  1 0 0 0 0 0 1 0 0 0 ...
 $ BedroomAbvGr : int  3 2 2 2 3 3 3 3 4 4 ...
 $ KitchenAbvGr : int  1 2 1 1 1 1 1 1 1 2 ...
 $ KitchenQual  : Ord.factor w/ 5 levels "Po"<"Fa"<"TA"<..: 4 3 4 5 4 3 3 4 3 3 ...
 $ TotRmsAbvGrd : int  8 5 6 4 7 6 6 6 7 8 ...
 $ Functional   : Ord.factor w/ 8 levels "Sal"<"Sev"<"Maj2"<..: 8 8 8 8 8 8 8 8 8 8 ...
 $ Fireplaces   : int  0 2 0 0 1 0 0 2 1 0 ...
 $ FireplaceQu  : Ord.factor w/ 6 levels "None"<"Po"<"Fa"<..: 1 4 1 1 5 1 1 4 5 1 ...
 $ GarageType   : Factor w/ 7 levels "2Types","Attchd",..: 2 2 2 6 2 2 2 2 2 7 ...
 $ GarageYrBlt  : int  2003 1939 2006 1920 2006 1977 1970 1977 1948 NA ...
 $ GarageFinish : Ord.factor w/ 4 levels "None"<"Unf"<"RFn"<..: 3 3 3 2 3 2 3 3 2 1 ...
 $ GarageCars   : int  2 1 2 1 3 1 1 2 1 0 ...
 $ GarageArea   : num  548 205 632 216 824 328 286 480 312 0 ...
 $ GarageQual   : Ord.factor w/ 6 levels "None"<"Po"<"Fa"<..: 4 5 4 3 4 4 4 4 4 1 ...
 $ GarageCond   : Ord.factor w/ 6 levels "None"<"Po"<"Fa"<..: 4 4 4 4 4 4 4 4 4 1 ...
 $ PavedDrive   : Ord.factor w/ 4 levels "None"<"N"<"P"<..: 4 4 4 2 4 4 4 4 4 4 ...
 $ WoodDeckSF   : num  0 0 105 0 144 210 238 168 0 0 ...
 $ OpenPorchSF  : num  61 4 61 20 104 0 0 68 0 0 ...
 $ EnclosedPorch: num  0 0 0 94 0 0 0 0 108 0 ...
 $ X3SsnPorch   : num  0 0 0 0 0 0 0 0 0 0 ...
 $ ScreenPorch  : num  0 0 0 0 0 0 0 0 0 0 ...
 $ PoolArea     : num  0 0 0 0 0 0 0 0 0 0 ...
 $ PoolQC       : Ord.factor w/ 6 levels "None"<"Po"<"Fa"<..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Fence        : Factor w/ 5 levels "GdPrv","MnPrv",..: 5 5 5 5 5 5 5 5 5 5 ...
 $ MiscFeature  : Factor w/ 6 levels "Elev","Gar2",..: 6 6 6 6 6 6 6 6 6 6 ...
 $ MiscVal      : num  0 0 0 0 0 0 0 0 0 0 ...
 $ MoSold       : int  2 1 2 1 7 11 8 2 8 5 ...
 $ YrSold       : int  2008 2008 2010 2007 2008 2008 2009 2010 2008 2010 ...
 $ SaleType     : Factor w/ 10 levels "WD","CWD","VWD",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ SaleCondition: Factor w/ 6 levels "Normal","Abnorml",..: 1 1 1 2 1 1 1 1 1 1 ...
 $ SalePrice    : num  208500 118000 206000 86000 232000 ...

There are 715 total observations in the validation training set, across 81 features (target feature “Saleprice” and id column “Id” included). 46 features are factors, 24 of which are ordered. 14 are integers. 20 are doubles, including “SalePrice”. (“Id” is integers cast as a character type.)

Correlations

The full correlation grid is too large for most screens, but there are only a handful of noteworthy correlations which I’ll include with further analysis of each feature.

# ggcorr(
#   select(val_train_Xy, where(is.numeric)),
#   label = T,
#   label_round = 2,
#   label_size = 3
# )

Normalizing Continuous Variables

I wrote a simple algorithm to try various transformations on each continuous feature and use the Shapiro-Wilk Normality Test to choose the best transformation. I then visualize each feature and decide if it even makes sense to attempt normalization.

I should have made logarithmic transformations be that of x+1, but instead I excluded 0s, which only partially handled the issue. 1s still convert to 0s in that case. The result was that variables with a substantial number of 1s did not find logs very useful for normalization.

I also did not include more dynamic transformations like Box-Cox. The script could be modified to include them.

For variables in which a 0 indicates a missing feature, I normalized only the non-zero set. The idea was that it might aid regression when the variable is put into interaction with its missingness.

funcs_lst = list(
    'no_func' = function (x) { x },
    'sqrt' = sqrt,
    'cbrt' = function(x) { x^(1/3) },
    'square' = function(x) { x^2 },
###
### FIXME
# Make log transformations of x+1.
###
    'log' = log,
    'log2' = log2,
    'log10' = log10,
    '1/x' = function (x) { 1/x },
    '2^(1/x)' = function (x) { 2^(1/x) }
    # Box Cox: write function that calls MASS::boxcox and include lambda in results/function returned.
    # Yeo-Johnson
    # Winsorize here?
    # Rank
    # Rank-Gauss
  )
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

print("Best normalizing transformations:")
[1] "Best normalizing transformations:"
for (feat in names(best_normalizers)) {
  func_name = best_normalizers[[feat]]$best_func$name
  print(
    paste(
      feat, ":", func_name,
      ", p-value:", best_normalizers[[feat]]$results[[func_name]]$p.value
    )
  )
}
[1] "LotFrontage : log10 , p-value: 0.896193826437694"
[1] "LotArea : log10 , p-value: 2.26152919548748e-17"
[1] "YearBuilt : square , p-value: 0.0108822707033521"
[1] "YearRemodAdd : no_func , p-value: 0.0256596409501691"
[1] "MasVnrArea : cbrt , p-value: 0.954476102599975"
[1] "BsmtFinSF1 : cbrt , p-value: 4.83197463170721e-05"
[1] "BsmtFinSF2 : cbrt , p-value: 0.576669464478684"
[1] "BsmtUnfSF : cbrt , p-value: 0.00100793900881088"
[1] "TotalBsmtSF : log , p-value: 2.08019611846809e-06"
[1] "X1stFlrSF : log2 , p-value: 0.0309515511380178"
[1] "X2ndFlrSF : sqrt , p-value: 0.580431655244041"
[1] "LowQualFinSF : sqrt , p-value: 0.12996282240508"
[1] "GrLivArea : log2 , p-value: 0.0129611332961232"
[1] "BsmtFullBath :  , p-value: "
[1] "BsmtHalfBath :  , p-value: "
[1] "FullBath :  , p-value: "
[1] "HalfBath :  , p-value: "
[1] "BedroomAbvGr : sqrt , p-value: 0.995915265851324"
[1] "KitchenAbvGr :  , p-value: "
[1] "TotRmsAbvGrd : no_func , p-value: 0.972016654577295"
[1] "Fireplaces :  , p-value: "
[1] "GarageYrBlt : square , p-value: 0.00823549702716429"
[1] "GarageCars : no_func , p-value: 0.971877057620897"
[1] "GarageArea : sqrt , p-value: 0.203272774347792"
[1] "WoodDeckSF : cbrt , p-value: 0.991997742575695"
[1] "OpenPorchSF : cbrt , p-value: 0.898709370969093"
[1] "EnclosedPorch : no_func , p-value: 0.526291715345031"
[1] "X3SsnPorch : sqrt , p-value: 0.516253205278421"
[1] "ScreenPorch : cbrt , p-value: 0.608040279289975"
[1] "PoolArea :  , p-value: "
[1] "MiscVal : log2 , p-value: 0.280656484036341"
[1] "MoSold : no_func , p-value: 0.875731443365881"
[1] "YrSold : no_func , p-value: 0.96717393596804"
[1] "SalePrice : log , p-value: 0.72923438230646"

SalePrice

Back to top.

Normalize

x = 'SalePrice'
summary(val_train_Xy[[x]])
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  39300  129950  160000  181697  212500  745000 
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 5000,
  t_binw = 1/50
)
NULL

val_train_Xy = val_train_Xy %>%
  mutate('log(SalePrice)' = log(SalePrice))

x = 'log(SalePrice)'

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 log(SalePrice) 
 Min.   :10.58  
 1st Qu.:11.77  
 Median :11.98  
 Mean   :12.03  
 3rd Qu.:12.27  
 Max.   :13.52  
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1/100,
  t_binw = 1/100
)
NULL

A natural log best normalizes the sale price distribution. However, because it isn’t a log10 transformation, it won’t precisely scale the prediction errors proportionally to the sale price. I could simply use the log10 instead, which does a fair job at normalizing as well, but I want to stick with the “best” transformation to make the best model. So, when I run ML, I will write a custom summary function to train with which simply divides the error by the price (the log(SalePrice)) before calculating the RMSE.

Winsorize

Here I’m looking for the best Winsorization quantile values of the best transformation – the best according to Shapiro-Wilk p-values. I could have programmatically explored the space and returned the best result. But, I want to visualize it and explore the process itself. In future projects, I might choose to further automate this.

It should be noted that Winsorization of the target variable should only be used for training, not for testing. A log transformation can be reversed as a vectorized operation, but Winsorization can’t. Winsorization should improve the accuracy of the model, but would be cheating on the test.

qqnorm(y = val_train_Xy$SalePrice, ylab = 'SalePrice')
qqline(y = val_train_Xy$SalePrice, ylab = 'SalePrice')


qqnorm(y = val_train_Xy$`log(SalePrice)`, ylab = 'log(SalePrice)')
qqline(y = val_train_Xy$`log(SalePrice)`, ylab = 'log(SalePrice)')


Win_log_x = Winsorize(
  x = val_train_Xy[['log(SalePrice)']],
  probs = c(0.005, 0.995)
)

qqnorm(y = Win_log_x, ylab = 'Win_log_x')
qqline(y = Win_log_x, ylab = 'Win_log_x')


Win_raw_x = Winsorize(
  x = val_train_Xy[['SalePrice']],
  probs = c(0, 0.95)
)

qqnorm(y = Win_raw_x, ylab = 'Win_raw_x')
qqline(y = Win_raw_x, ylab = 'Win_raw_x')


print(shapiro.test(x = val_train_Xy$SalePrice))

    Shapiro-Wilk normality test

data:  val_train_Xy$SalePrice
W = 0.86089, p-value < 2.2e-16
print(shapiro.test(x = val_train_Xy$`log(SalePrice)`))

    Shapiro-Wilk normality test

data:  val_train_Xy$`log(SalePrice)`
W = 0.9904, p-value = 0.0001299
print(shapiro.test(x = Win_log_x))

    Shapiro-Wilk normality test

data:  Win_log_x
W = 0.99062, p-value = 0.0001609
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.93275, p-value < 2.2e-16

A small Winsorization of the log best normalizes the variable (W = 0.99062). It doesn’t pass the test for normality (p < 0.01), but it is still better prepared for a linear regression.

val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(SalePrice)' = Winsorize(
      SalePrice,
      probs = c(0, 0.95),
      na.rm = T
    )
  ) %>%
  mutate(
    'Win(log(SalePrice))' = Winsorize(
      log(SalePrice),
      probs = c(0.005, 0.995),
      na.rm = T
    )
  )

Correlations

Here are the correlations between SalePrice and the rest of the variables, compared to those of the transformed variables. Transforming SalePrice resulted in minor increases of correlations to many other continuous features and some minor reductions of correlations, an overall minor and insignificant improvement. But, this is the first of the variables to be transformed.

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x = 'Win(log(SalePrice))'
x_lst = c('SalePrice', 'log(SalePrice)', 'Win(log(SalePrice))',
          'Win(SalePrice)')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
   SalePrice        log(SalePrice)     Win(log(SalePrice))
 Min.   :0.007746   Min.   :0.001275   Min.   :0.001406   
 1st Qu.:0.124877   1st Qu.:0.130580   1st Qu.:0.131486   
 Median :0.306100   Median :0.314440   Median :0.314346   
 Mean   :0.310147   Mean   :0.322002   Mean   :0.322153   
 3rd Qu.:0.520650   3rd Qu.:0.538367   3rd Qu.:0.539042   
 Max.   :0.689569   Max.   :0.687804   Max.   :0.686782   
 Win(SalePrice)    
 Min.   :0.006338  
 1st Qu.:0.116871  
 Median :0.312263  
 Mean   :0.317893  
 3rd Qu.:0.515569  
 Max.   :0.686731  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')

Hard Code

I’ll need to hard code the top and bottom limits for the engineering script to apply to the test set without leakage. And, I’ll need to drop Win(SalePrice).

I want to keep the transformed and non-Winsorized version though. In the case of the target variable, I’ll train on the Winsorized variable and test on the transformed because I can reverse the transformation. In the case of predictor variables, the Winsorized version will be good for basic linear regression without interactions, but not necessarily for KNN and RF which may be able to use the outliers for clustering and grouping.

x = 'Win(log(SalePrice))'

min_val = min(val_train_Xy[[x]])
max_val = max(val_train_Xy[[x]])
print(paste("min_val:", min_val))
[1] "min_val: 10.9579480025541"
print(paste("max_val:", max_val))
[1] "max_val: 13.2279465702719"
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(log(SalePrice))' = Winsorize(
      .data[['log(SalePrice)']],
      minval = min_val,
      maxval = max_val
    )
  ) %>%
  select(-c('Win(SalePrice)'))

gg = ggplot(val_train_Xy, aes(x = .data[[x]]))
p1 = gg + geom_histogram(binwidth = 1/50)
p2 = gg + geom_boxplot(notch = T)
grid.arrange(p1, p2)

SalePrice as Factor

To aid visualization, I’ll create a SalePrice factor with extremes and quartiles as levels.

val_train_Xy = val_train_Xy %>%
  mutate(
    'SalePrice.fact' = cut(
      x = SalePrice,
      breaks = quantile(x = SalePrice),
      include.lowest = T,
      ordered_result = T
    )
  )

summary(val_train_Xy$SalePrice.fact)
 [3.93e+04,1.3e+05]   (1.3e+05,1.6e+05]  (1.6e+05,2.12e+05] 
                179                 179                 178 
(2.12e+05,7.45e+05] 
                179 

MSSubClass (Dwelling Type)

Back to top.

I’ll use log(SalePrice) to visualize against factors, rather than the Winsorized version.

x = 'MSSubClass'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "60"  "20"  "30"  "80"  "160" "50"  "70"  "120"

The most common class is 1-story newer than 1945 (267), followed by 2-story newer than 1945 (149) and 1.5-story finished all ages (67). The priciest class is 2-story newer than 1945, though some classes are so uncommon that it’s hard to say this completely confidently.

This feature is a mix of information mostly covered by HouseStyle, YearBuilt, and square footage. It might be worth dropping it to avoid overweighting this info, avoid spurious fit, and skip the compute cost of 16 one-hot features. Alternatively, decomposition with PCA might help pull out the unique information regarding PUD housing.

MSZoning

Back to top.

x = 'MSZoning'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "RL" "RM" "FV"

Mostly residential low density (574), some medium density (100), fewer floating village residential (flexible zoning, 31). Predictive power may be limited due to lack of diversity. That said, low-density residential and floating village clearly tend to sell for more than medium-density. Consider only one-hot encoding RL, RM, and FV?

LotFrontage

Back to top.

Normalize

x = 'LotFrontage'

summary(val_train_Xy[[x]])
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  21.00   58.00   70.00   70.04   80.00  313.00     133 
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 5,
  t_binw = 1/50
)
NULL

A log10 scale centers it better (133 missing values excluded).

Note the extreme spike (~65 observations) in left of mode (70-75 SF) at 60 SF. It doesn’t seem to be associated with any particular neighborhood or lot configuration or anything, but probably just a common way to cut lots.

The feature could benefit from top/bottom coding.

133 NAs. Counterintuitively, a lower proportion of missing LotFrontages are inside lots (55/121 in the NA subset vs. 511/715 in the training set [these numbers are from a previous split and not accurate for the current data set]), whereas many lots that by definition have frontage (44 corner lots, FR2, and FR3) are missing frontage values. Could use LotArea, LotShape, LotConfig, and (?) (all of which aren’t missing values) to multivariate impute.

x = 'log10(LotFrontage)'
val_train_Xy = val_train_Xy %>%
  mutate(
    'log10(LotFrontage)' = ifelse(
    LotFrontage == 0,
    0,
    log10(LotFrontage)
    )
  )

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 log10(LotFrontage)
 Min.   :1.322     
 1st Qu.:1.763     
 Median :1.845     
 Mean   :1.820     
 3rd Qu.:1.903     
 Max.   :2.496     
 NA's   :133       
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1/50,
  t_binw = 1/50
)
NULL

Winsorize

qqnorm(y = val_train_Xy$LotFrontage, ylab = 'LotFrontage')
qqline(y = val_train_Xy$LotFrontage, ylab = 'LotFrontage')


qqnorm(y = val_train_Xy[[x]], ylab = x)
qqline(y = val_train_Xy[[x]], ylab = x)


Win_log10_x = Winsorize(
  x = val_train_Xy[[x]],
  probs = c(0.05, 0.99),
  na.rm = T
)

qqnorm(y = Win_log10_x, ylab = 'Win_log10_x')
qqline(y = Win_log10_x, ylab = 'Win_log10_x')


Win_raw_x = Winsorize(
  x = val_train_Xy$LotFrontage,
  probs = c(0.05, 0.95),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'Win(LotFrontage)')
qqline(y = Win_raw_x, ylab = 'Win(LotFrontage)')


print(shapiro.test(x = val_train_Xy$LotFrontage))

    Shapiro-Wilk normality test

data:  val_train_Xy$LotFrontage
W = 0.87621, p-value < 2.2e-16
print(shapiro.test(x = val_train_Xy[[x]]))

    Shapiro-Wilk normality test

data:  val_train_Xy[[x]]
W = 0.94238, p-value = 3.051e-14
print(shapiro.test(x = Win_log10_x))

    Shapiro-Wilk normality test

data:  Win_log10_x
W = 0.97393, p-value = 1.16e-08
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.9771, p-value = 6.718e-08

It looks like just Winsorizing the raw variable may be the way to go here.

val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(LotFrontage)' = Winsorize(
      LotFrontage,
      probs = c(0.05, 0.95),
      na.rm = T
      )
    ) %>%
  mutate(
    'Win(log10(LotFrontage))' = Winsorize(
      log10(LotFrontage),
      probs = c(0.05, 0.99),
      na.rm = T
      )
    )

Correlations

x = 'Win(LotFrontage)'
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('LotFrontage', 'log10(LotFrontage)', 'Win(log10(LotFrontage))', 'Win(LotFrontage)')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
  LotFrontage       log10(LotFrontage) Win(log10(LotFrontage))
 Min.   :0.001316   Min.   :0.002122   Min.   :0.002136       
 1st Qu.:0.052789   1st Qu.:0.036725   1st Qu.:0.036057       
 Median :0.152575   Median :0.123545   Median :0.121036       
 Mean   :0.189170   Mean   :0.163383   Mean   :0.162507       
 3rd Qu.:0.318210   3rd Qu.:0.285571   3rd Qu.:0.300117       
 Max.   :0.515867   Max.   :0.466945   Max.   :0.430664       
 Win(LotFrontage) 
 Min.   :0.00368  
 1st Qu.:0.05062  
 Median :0.13177  
 Mean   :0.17398  
 3rd Qu.:0.31994  
 Max.   :0.44127  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


y_lst = c('log(SalePrice)')
for (feat in x_lst) {
  plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst)
}

Hard Code

x = 'Win(LotFrontage)'

min_val = min(val_train_Xy[!is.na(val_train_Xy[[x]]), x])
max_val = max(val_train_Xy[!is.na(val_train_Xy[[x]]), x])
print(paste("min_val:", min_val))
[1] "min_val: 34.05"
print(paste("max_val:", max_val))
[1] "max_val: 108"
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(LotFrontage)' = Winsorize(
      LotFrontage,
      minval = min_val,
      maxval = max_val
    )
  )

gg = ggplot(val_train_Xy, aes(x = .data[[x]]))
p1 = gg + geom_histogram(binwidth = 1)
p2 = gg + geom_boxplot(notch = T)
grid.arrange(p1, p2)

By Factors

y_lst = c('MSSubClass', 'MSZoning', 'LotShape', 'LotConfig', 'Neighborhood',
          'BldgType', 'HouseStyle')
for (y in y_lst) {
  plt = fenced_jbv(
    data = val_train_Xy, x = y, y = 'log10(LotFrontage)') +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))
  print(plt)
}

Overall, the clusters of lots at 60’ and 80’ is quite apparent.

Unsurprisingly, low-density residential tends to have more lot frontage than medium-density residential. Slightly irregular lots might tend to have more frontage than regular, but we can’t say that with much confidence. Corner lots have more frontage than inside lots. There’s quite a bit of variation between neighborhoods.

Looking at MSSubClass, older homes tend to have less lot frontage than their equivalent house styles after 1945, unless they are PUD homes, which tend to have much less frontage than the rest of the classes. This connection is not visible in YearBuilt, which has no correlation to LotFrontage.

There’s also an interesting gap between 50’ and 60’ that only homes older than 1945 tend to fill, except for PUD homes.

fenced_jbv(
  data = val_train_Xy,
  x = 'MSSubClass',
  y = 'log10(LotFrontage)',
  jit_col = 'SalePrice.fact',
  leg_lbl = 'SalePrice',
  jit_alpha = 0.5,
  box_color = 'red'
)


ggplot(val_train_Xy, aes(x = `log10(LotFrontage)`, y = `log(SalePrice)`)) +
  geom_point(alpha = 0.5) +
  geom_smooth(method = 'lm') +
  facet_wrap(vars(MSSubClass), ncol = 5)

LotArea

Back to top.

Normalize

x = 'LotArea'
summary(val_train_Xy[[x]])
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1596    7610    9477   10346   11611  115149 
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 200,
  t_binw = 1/50
)
NULL

x_trans = 'log10(LotArea)'
val_train_Xy = val_train_Xy %>%
  mutate('log10(LotArea)' = log10(LotArea))

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x_trans])
 log10(LotArea) 
 Min.   :3.203  
 1st Qu.:3.881  
 Median :3.977  
 Mean   :3.960  
 3rd Qu.:4.065  
 Max.   :5.061  
sum_and_trans_cont(
  data = val_train_Xy,
  x = x_trans,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1/50,
  t_binw = 1/500
)
NULL

x_trans = 'log10(log10(LotArea))'
val_train_Xy = val_train_Xy %>%
  mutate('log10(log10(LotArea))' = log10(log10(LotArea)))

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x_trans])
 log10(log10(LotArea))
 Min.   :0.5056       
 1st Qu.:0.5890       
 Median :0.5995       
 Mean   :0.5970       
 3rd Qu.:0.6090       
 Max.   :0.7043       
sum_and_trans_cont(
  data = val_train_Xy,
  x = x_trans,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1/500,
  t_binw = 1/750
)
NULL

I doubt it’s worth doing the third log10 transformation now that the median and mean are so close. It still needs top- and bottom-coding anyway.

Even the second transformation might lead to overfit, but I’ll roll with it.

Winsorize

qqnorm(y = val_train_Xy$LotArea, ylab = 'LotArea')
qqline(y = val_train_Xy$LotArea, ylab = 'LotArea')


qqnorm(y = val_train_Xy$`log10(LotArea)`, ylab = 'log10(LotArea)')
qqline(y = val_train_Xy$`log10(LotArea)`, ylab = 'log10(LotArea)')


qqnorm(
  y = val_train_Xy$`log10(log10(LotArea))`,
  ylab = 'log10(log10(LotArea))'
)
qqline(
  y = val_train_Xy$`log10(log10(LotArea))`,
  ylab = 'log10(log10(LotArea))'
)


Win_log10log10_x = Winsorize(
  x = val_train_Xy$`log10(log10(LotArea))`,
  probs = c(0.05, 0.99),
  na.rm = T
)

qqnorm(y = Win_log10log10_x, ylab = 'Win(log10(log10(LotArea)))')
qqline(y = Win_log10log10_x, ylab = 'Win(log10(log10(LotArea)))')


Win_log10_x = Winsorize(
  x = val_train_Xy$`log10(LotArea)`,
  probs = c(0.05, 0.99),
  na.rm = T
)

qqnorm(y = Win_log10_x, ylab = 'Win(log10(LotArea))')
qqline(y = Win_log10_x, ylab = 'Win(log10(LotArea))')


Win_raw_x = Winsorize(
  x = val_train_Xy$LotArea,
  probs = c(0.01, 0.95),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'LotArea')
qqline(y = Win_raw_x, ylab = 'LotArea')


print(shapiro.test(x = val_train_Xy$LotArea))

    Shapiro-Wilk normality test

data:  val_train_Xy$LotArea
W = 0.5211, p-value < 2.2e-16
print(shapiro.test(x = val_train_Xy$`log10(LotArea)`))

    Shapiro-Wilk normality test

data:  val_train_Xy$`log10(LotArea)`
W = 0.9113, p-value < 2.2e-16
print(shapiro.test(x = val_train_Xy$`log10(log10(LotArea))`))

    Shapiro-Wilk normality test

data:  val_train_Xy$`log10(log10(LotArea))`
W = 0.90238, p-value < 2.2e-16
print(shapiro.test(x = Win_log10log10_x))

    Shapiro-Wilk normality test

data:  Win_log10log10_x
W = 0.94232, p-value = 4.825e-16
print(shapiro.test(x = Win_log10_x))

    Shapiro-Wilk normality test

data:  Win_log10_x
W = 0.94434, p-value = 9.78e-16
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.97759, p-value = 5.239e-09

It looks like simply Winsorizing the base variable might be best.

val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(LotArea)' = Winsorize(
      LotArea,
      probs = c(0.01, 0.95),
      na.rm = T
      )
    ) %>%
  mutate(
    'Win(log10(LotArea))' = Winsorize(
      log10(LotArea),
      probs = c(0.05, 0.99),
      na.rm = T
      )
    )

Correlations

Transforming LotArea with log10 resulted in bigger swings in r in both directions, but no real change in aggregate. The additional log10 transformation produced minor changes in correlation compared to the initial transformation, mostly toward less correlation.

Unsurprisingly, transformed LotArea is much more correlated to transformed LotFrontage than their untransformed counterparts (r went from 0.51 to 0.73). Also, transformed LotArea and transformed SalePrice correlate a little better than their untransformed counterparts, but are still weakly correlated (r increased from 0.30 to 0.37).

It also became noticeably more correlated to square footage of the first floor, bedrooms / total rooms above ground, and garage area/cars. Like previous transformations, some correlations lessened with this transformation, but none so much that the correlation dropped a bracket, e.g. from weak to insignificant.

The distribution of correlations dropped with the second transformation. I’ll only use the first transformation in the engineering script.

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('LotArea', 'log10(LotArea)', 'log10(log10(LotArea))', 'Win(log10(LotArea))', 'Win(LotArea)')

x = 'Win(LotArea)'

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(df)
    LotArea          log10(LotArea)     log10(log10(LotArea))
 Min.   :0.0008156   Min.   :0.001463   Min.   :0.001822     
 1st Qu.:0.0298980   1st Qu.:0.038420   1st Qu.:0.038591     
 Median :0.1411549   Median :0.128146   Median :0.125858     
 Mean   :0.1692131   Mean   :0.210784   Mean   :0.208681     
 3rd Qu.:0.2935533   3rd Qu.:0.351718   3rd Qu.:0.343608     
 Max.   :0.5149010   Max.   :0.728610   Max.   :0.741522     
 Win(log10(LotArea))  Win(LotArea)      
 Min.   :0.005534    Min.   :0.0001219  
 1st Qu.:0.045043    1st Qu.:0.0704055  
 Median :0.124050    Median :0.1270071  
 Mean   :0.216047    Mean   :0.2244330  
 3rd Qu.:0.359245    3rd Qu.:0.3686740  
 Max.   :0.697739    Max.   :0.6861456  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


y_lst = c('log(SalePrice)')
for (feat in x_lst) {
  plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst)
}

Hard Code

x = 'Win(LotArea)'

min_val = min(val_train_Xy[!is.na(val_train_Xy[[x]]), x])
max_val = max(val_train_Xy[!is.na(val_train_Xy[[x]]), x])
print(paste("min_val:", min_val))
[1] "min_val: 1975.96"
print(paste("max_val:", max_val))
[1] "max_val: 16946.4"
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(LotArea)' = Winsorize(
      LotArea,
      minval = min_val,
      maxval = max_val
    )
  ) %>%
  select(-c('log10(LotArea)', 'Win(log10(LotArea))'))

gg = ggplot(val_train_Xy, aes(x = .data[[x]]))
p1 = gg + geom_histogram(binwidth = 100)
p2 = gg + geom_boxplot(notch = T)
grid.arrange(p1, p2)

y_lst = c('log(SalePrice)', 'Win(LotFrontage)', 'X1stFlrSF',
          'GrLivArea','TotRmsAbvGrd', 'GarageArea')
x = 'Win(LotArea)'
plot_scat_pairs(df = val_train_Xy, x = x, y_lst = y_lst)
NULL

By Factors

y_lst = c('MSSubClass', 'MSZoning', 'LotShape', 'LotConfig', 'Neighborhood',
          'BldgType', 'HouseStyle')
for (y in y_lst) {
  plt = fenced_jbv(
    data = val_train_Xy,
    x = y,
    y = 'log10(log10(LotArea))',
    jit_h = 0 # Again R randomly decides to go wonk unless I enter the default.
  ) +
    theme(axis.text.x = element_text(angle = 45, hjust=1))
  print(plt)
}

There are similar patterns as in LotFrontage, with a more marked upward trend against LotShape, and with less difference between lot configurations. There’s also an interesting pocket of two-story houses with low lot area; it’s not worth plotting, but you can see which neighborhoods these are.

Street

Back to top.

Really lopsided to paved (3 gravel, 712 paved). Drop this feature.

summary(val_train_Xy$Street)
None Grvl Pave 
   0    3  712 
val_train_Xy = select(val_train_Xy, -c('Street'))

Alley

Back to top.

Vast majority have none, drop it.

summary(val_train_Xy$Alley)
None Grvl Pave 
 669   24   22 
val_train_Xy = select(val_train_Xy, -c('Alley'))

LotShape

Back to top.

x = 'LotShape'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "Reg" "IR1"

Irregularly shaped lots tend to sell for more. Good candidate for binarization if doing a basic linear regression with no interactions; one-hot encode ‘Reg’ and drop the rest of the levels.

LandContour

Back to top.

summary(val_train_Xy$LandContour)
Lvl Bnk HLS Low 
642  33  24  16 
val_train_Xy = select(val_train_Xy, -c('LandContour'))

Utilities

Back to top.

All all-public. Definitely drop.

summary(val_train_Xy$Utilities)
  None    ELO NoSeWa NoSewr AllPub 
     0      0      0      0    715 
val_train_Xy = select(val_train_Xy, -c('Utilities'))

LotConfig

Back to top.

x = 'LotConfig'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "Inside"  "Corner"  "CulDSac"

This set is mostly inside lots (513) but plenty of corner lots (124). I’ve always thought corner lots are prized, but it doesn’t seem to significantly add to price compared to an inside lot.

Cul de sacs are the priciest. This is somewhat a proxy for neighborhood (and maybe other features) as it is true across most neighborhoods except those that are already pricey (where cul de sacs are relatively more common) or least pricey (where cul de sacs don’t typically exist).

ggplot(
  data = val_train_Xy,
  # data = filter(
  #   val_train_Xy,
  #   LotConfig %in% c('Inside', 'Corner', 'CulDSac')
  # ),
  mapping = aes(
    x = Neighborhood,
    y = `log(SalePrice)`,
  )
) +
  geom_jitter(
    alpha = .4,
    mapping = aes(color = LotConfig, shape = LotConfig)
  ) +
  geom_boxplot(notch = T, varwidth = T, alpha = 0) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))


ggplot(
  data = val_train_Xy,
  mapping = aes(
    x = Neighborhood,
    y = `log(SalePrice)`,
  )
) +
  geom_jitter(
    alpha = .3,
    mapping = aes(color = LotShape, shape = LotShape)
  ) +
  geom_boxplot(notch = T, varwidth = T, alpha = 0) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))


ggplot(
  data = val_train_Xy,
  mapping = aes(
    x = LotConfig,
    y = `log(SalePrice)`,
  )
) +
  geom_jitter(
    alpha = .4,
    mapping = aes(color = LotShape, shape = LotShape)
  ) +
  geom_boxplot(notch = T, varwidth = T, alpha = 0)

ggplot(
  data = val_train_Xy,
  mapping = aes(
    x = LotShape,
    y = `log(SalePrice)`,
  )
) +
  geom_jitter(
    alpha = .4,
    mapping = aes(color = LotConfig, shape = LotConfig)
  ) +
  geom_boxplot(notch = T, varwidth = T, alpha = 0)

There appears to be some overlap with lot shape and lot configuration.

LandSlope

Back to top.

Vast majority are gentle drop it.

summary(val_train_Xy$LandSlope)
None  Gtl  Mod  Sev 
   0  677   32    6 
val_train_Xy = select(val_train_Xy, -c('LandSlope'))

Neighborhood

Back to top.

x = 'Neighborhood'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)

y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    keysize = 2
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
 [1] "CollgCr" "BrkSide" "OldTown" "Somerst" "NWAmes"  "Sawyer"  "Edwards"
 [8] "Crawfor" "NridgHt" "Gilbert" "Names"  

North Ames (Names) and CollgCr have the most residential homes (107 and 76). but they’re also zoned low-density. A handful of neighborhoods have almost no houses in this set.

The priciest neighborhoods are StoneBr, NridgeHt, and NoRidge. The least pricey (OldTown, MeadowV, and IDOTRR) are also the most dense and commercial. There are significant differences in prices between many neighborhoods, and not just between the cheapest and priciest.

ggplot(
  data = val_train_Xy,
  aes(x = Neighborhood, fill = MSZoning)
) +
  geom_bar() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Condition1, Condition2

Back to top.

The vast majority are normal. Drop Condition2. But, for Condition1, there appear to be significant differences in SalePrice between Norm and Feedr, and maybe between Norm and Artery but there are too few Artery observations. It might be worth one-hot-encoding those categories.

x = 'Condition1'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "Norm"  "Feedr"

You might consider binarizing, lumping ‘Feedr’ and ‘Artery’ and maybe ‘RRAe’ together and lumping the rest with ‘Norm’. I’ll try it out during feature selection with caret in the ML phase.

summary(val_train_Xy$Condition2)
Artery  Feedr   Norm   RRNn   RRAn   PosN   PosA   RRNe   RRAe 
     1      2    709      1      0      0      1      0      1 
val_train_Xy = select(val_train_Xy, -c('Condition2'))

BldgType

Back to top.

x = 'BldgType'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
character(0)

Majority single-family (603), 24 NAs. It seems like an inherently important feature, despite the lopsidedness of the distribution. Probably safe to impute to mode (1Family), but other features might inform, such as MSSubClass, MSZoning, Neighborhood, HouseStyle, and building materials; multivariate impute might be in order, if keeping the feature in the first place.

Duplexes and two-family are significantly cheaper than single-family and townhouses, if accepting the low number in the sample. Candidate for binarization, but may lose interactions.

HouseStyle

Back to top.

x = 'HouseStyle'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "2Story" "1Story" "SLvl"   "1.5Fin"

Mostly one-story (358), but many two-story (220). Several significant price differences across groups. Maybe worth keeping, but also kind of noisy with the finished/unfinished business only applying to half-stories, and number of stories and finished status are encoded in other features.

OverallQual

Back to top.

x = 'OverallQual'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "7" "5" "8" "4" "6"

There’s a very strong relationship to SalePrice.

Normalize

It’s a pretty normal distribution, slightly left-skewed. Mode (2199 5s) left of median/mean (180 6s), few 1s, 2s, and 3s. It might be worth casting as an integer and transforming to normalize and possibly improve the correlation.

x = 'OverallQual_int'

val_train_Xy = val_train_Xy %>%
  mutate(OverallQual_int = as.integer(OverallQual))

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 OverallQual_int 
 Min.   : 1.000  
 1st Qu.: 5.000  
 Median : 6.000  
 Mean   : 6.092  
 3rd Qu.: 7.000  
 Max.   :10.000  
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1,
  t_binw = 1
)
NULL

None of the transformations improved its distribution.

Correlations

Here are the correlations between OverallQual_int and the rest of the variables.

This feature has several moderate correlations to features having to do with size and age. It also has a strong correlation to transformed SalePrice (0.82).

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c(x)

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
 OverallQual_int  
 Min.   :0.01751  
 1st Qu.:0.13440  
 Median :0.27000  
 Mean   :0.31424  
 3rd Qu.:0.54465  
 Max.   :0.82099  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')

y_lst = c('log(SalePrice)', 'YearBuilt', 'YearRemodAdd', 'TotalBsmtSF',
          'GrLivArea','FullBath', 'TotRmsAbvGrd', 'GarageYrBlt', 'GarageCars',
          'GarageArea')
plot_scat_pairs(df = val_train_Xy, x = x, y_lst = y_lst)
NULL

By Factors

Again, there’s a lot of interaction with MSSubClass and Neighborhood. Quality also decreases with zoning density. Two-story houses are generally rated higher than one-story houses. Vinyl gets rated highest among exteriors. Simply having masonry improves quality rating. Poured concrete foundations on average receive 7s, whereas cinder block and brick/tile receive 5s on average. The same is true for attached and built-in garages compared to detached and no garages. It almost looks like it’s better to have no fence than to have one with minor privacy. Court officer deeds/estates are rated more poorly than warranty deeds and new sales. Abnormal sales are rated lower than normal sales.

Overall quality is doing a lot of the work for other features toward predicting price.

y_lst = c('MSSubClass', 'MSZoning', 'Neighborhood', 'BldgType', 'HouseStyle',
          'RoofMatl', 'RoofStyle', 'Exterior1st', 'MasVnrType', 'Foundation',
          'Heating', 'Electrical', 'Functional', 'GarageType', 'GarageFinish',
          'Fence', 'MiscFeature', 'SaleType', 'SaleCondition')
for (y in y_lst) {
  plt = fenced_jbv(data = val_train_Xy, x = y, y = x) +
    theme(axis.text.x = element_text(angle = 45, hjust=1))
  print(plt)
}

OverallCond

Back to top.

x = 'OverallCond'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "5" "6" "8" "4" "7"

OverallCond is like OverallQual, but with a much more pronounced mode (397 5s) left of median/mean (124 6s), probably due to wear and tear being more universal than quality construction.

The correlation to SalePrice is weaker, and 5s oddly seem to sell for more on average than higher-rated houses.

Normalize

x = 'OverallCond_int'

val_train_Xy = val_train_Xy %>%
  mutate(OverallCond_int = as.integer(OverallCond))

# Recalculate best normalizers. Might as well do them all, see if previous
# transformations benefit from further transformation, while we're at it.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 OverallCond_int
 Min.   :1.00   
 1st Qu.:5.00   
 Median :5.00   
 Mean   :5.55   
 3rd Qu.:6.00   
 Max.   :9.00   
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1,
  t_binw = 1
)
NULL

Correlations

This feature is only weakly correlated to a couple of age features. It has no linear correlation to SalePrice.

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c(x)

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
 OverallCond_int    
 Min.   :0.0008734  
 1st Qu.:0.0201988  
 Median :0.0386786  
 Mean   :0.0676805  
 3rd Qu.:0.1009599  
 Max.   :0.3591485  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')

y_lst = c('log(SalePrice)', 'YearBuilt', 'YearRemodAdd', 'GarageYrBlt')
plot_scat_pairs(df = val_train_Xy, x = x, y_lst = y_lst)
NULL

The bump in price among houses of average condition seems to have to do with a cluster of houses made in the late ’90s and early 2000s.

ggplot(
  data = val_train_Xy,
  mapping = aes(
    x = OverallCond_int,
    y = .data[['log(SalePrice)']],
    color = YearBuilt
  )
) +
  geom_jitter(alpha = 0.5) +
  geom_smooth() #+

  # facet_wrap(vars(BldgType))

By Factors

OldTown seems to be in relatively good shape, while Edwards has proportionately more houses in need of work and reconditioning. You can easily spot clustered 5s in the neighborhoods that likely grew up in the building boom of the 2000s.

y_lst = c('MSSubClass', 'MSZoning', 'Neighborhood', 'Condition1', 'BldgType',
          'HouseStyle', 'RoofStyle', 'RoofMatl', 'Exterior1st', 'MasVnrType',
          'BsmtExposure', 'Foundation', 'Heating', 'Electrical', 'Functional',
          'GarageType', 'GarageFinish', 'Fence', 'MiscFeature', 'SaleType',
          'SaleCondition')
for (y in y_lst) {
  plt = fenced_jbv(data = val_train_Xy, x = y, y = x) +
    theme(axis.text.x = element_text(angle = 45, hjust=1))
  print(plt)
}

Houses with metal and wood exteriors are typically in better condition than those with vinyl despite houses with vinyl siding typically being rated as higher quality and newer. Likewise, houses with cinder block foundations seem to fare better over time than those with poured concrete despite quality ratings, according to this data set, as do houses with detached garages compared to those with attached and built-in garages. Is this because older houses and lower-quality houses that are still standing are the ones that have received better maintenance?

p1 = ggplot(
  data = val_train_Xy,
  mapping = aes(
    y = OverallCond_int,
    x = Exterior1st,
    color = YearBuilt
  )
) +
  geom_jitter() +
  geom_boxplot(alpha = 0) +
  theme(axis.text.x = element_text(angle = 45, hjust=1))

p2 = ggplot(
  data = val_train_Xy,
  mapping = aes(
    y = OverallCond_int,
    x = Foundation,
    color = YearBuilt
  )
) +
  geom_jitter() +
  geom_boxplot(alpha = 0) +
  theme(axis.text.x = element_text(angle = 45, hjust=1))

p3 = ggplot(
  data = val_train_Xy,
  mapping = aes(
    y = OverallCond_int,
    x = GarageType,
    color = YearBuilt
  )
) +
  geom_jitter() +
  geom_boxplot(alpha = 0) +
  theme(axis.text.x = element_text(angle = 45, hjust=1))

grid.arrange(p1, p2, p3, ncol = 2)

YearBuilt

Back to top.

No transformations normalized the distribution. You can see the construction boom between WWII and the ’80s (with a dip in mid ’70s stagflation), followed by the relative explosion starting in the late ’90s and dropping with the housing crisis in the 2000s.

Consider grouping around modes into a factor and dummy coding to accommodate some ML algorithms that wouldn’t handle a polymodal distribution well.

x = 'YearBuilt'
summary(val_train_Xy[x])
   YearBuilt   
 Min.   :1872  
 1st Qu.:1954  
 Median :1972  
 Mean   :1971  
 3rd Qu.:2000  
 Max.   :2010  
# sum_and_trans_cont(
#   data = val_train_Xy,
#   x = x,
#   func = best_normalizers[[x]]$best_func$func,
#   func_name = best_normalizers[[x]]$best_func$name,
#   x_binw = 1,
#   t_binw = 1
# )

ggplot(val_train_Xy, aes(x = YearBuilt)) +
  geom_histogram(binwidth = 1)

Correlations

x = 'YearBuilt'
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c(x)

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
   YearBuilt        
 Min.   :0.0009914  
 1st Qu.:0.0534053  
 Median :0.1616352  
 Mean   :0.2334731  
 3rd Qu.:0.3684433  
 Max.   :0.8301510  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')

y_lst = colnames(select(val_train_Xy, where(is.numeric)))
for (y in y_lst) {
  for (x in x_lst) {
    plt = ggplot(
      select(val_train_Xy, all_of(c(x, y))),
      aes(x = .data[[x]], y = .data[[y]])
    ) +
      geom_jitter() +
      geom_smooth() +
      labs(x = x, y = y) +
      scale_x_continuous(breaks = seq(1880, 2010, 5)) +
      theme(axis.text.x = element_text(angle = 90, hjust = 1))
    print(plt)
  }
}

By Factors

y_lst = colnames(select(val_train_Xy, where(is.factor)))
for (y in y_lst) {
  plt = fenced_jbv(data = val_train_Xy, x = y, y = x) +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))
  print(plt)
}

A Story of Ames

Before looking at remodel year, this is the story I gather about houses purchased in this period based on the above visualizations of YearBuilt. I could try to weave the viz in with the narrative, but I’m not going to.

Houses and garages have gotten bigger over the years, with more bathrooms and less enclosed porch space. (I’m curious to see whether the increasing rarity of enclosed porch space led to it being a prized (priced) feature, or if it simply isn’t as valued now.) Newer homes tend to be valued more, and builders generally began to produce higher-quality houses.

Most of the houses built in the post-WWII boom were single-story, but that’s also when 2 1/2 stories became unheard-of. Split/multi-level and duplexes began to come on the scene. 1 1/2 stories had their heyday. Some of the houses from then and earlier make up the group of two-family conversions. It would be interesting to check remodel years to see when those conversions tended to take place.

As the century turned, prices grew exponentially. Two-story houses and PUD housing became more prevalent, and townhouses appeared for the first time. Zoning became less dense as new suburbs sprang up. Lots became more irregular. Cul de sacs and parks started to sprinkle in as feeder streets networked out. A handful of houses began to appear near railroads.

Even as masonry became a growing bling factor driving up overall quality, the age of plastic ushered in vinyl siding as the dominant exterior. The overall condition of houses from the turn of the century on is starkly average compared to older houses which are commonly in good shape. Checking against remodel years will likely explain some of this, but I’m curious to compare exterior conditions of different materials with regard to age.

The Lost Generation got brick and tile foundations. Boomers got cinder block and slabs. Gen x and Millenials got poured concrete. This enabled us to build into the sides of hills better and have more exposed basements with walkouts.

Most of the basementless houses were built during the post-WWII boom when ramblers were a common answer to the burgeoning dream of homeownership. Basement area continuously grew from then on, especially finished basement area as people began to live more underground. Kitchens and bedrooms began to be more common below ground.

Kitchens became more of a target for adding value with improved materials, construction, and appliances. Heating systems improved over the years as well. New electrical standards came into place in 1960.

When the ’80s hit, the automobiled society was in full swing, and it was rare to see less than a two-car garage built anymore, let alone a house without a garage. Rather than a detached secondary building, garages became part of the main house itself. It was more often a finished space for more than just housing and working on cars, but without the need for high-quality construction. Unpaved driveways were now out of the question, though.

After 2000, virtually no new houses had fences, at least none that were bought during the years this data set covers.

YearBuilt as Factor

I’ll condense the housing booms around their modes in a factor that I can one-hot encode to accommodate some ML algorithms that wouldn’t handle a polymodal distribution well. To maintain the model going forward, new periods will have to be identified and added.

# Three periods: < 1945 < 1985ish < 2010
val_train_Xy = val_train_Xy %>%
  mutate(
    YearBuilt.fact = cut(
      x = YearBuilt,
      breaks = c(-Inf, 1944, 1986, Inf),
      ordered_result = T,
      labels = c('Before_1945', '1945_1986', 'After_1986')
    )
  )

p1 = ggplot(val_train_Xy, aes(x = YearBuilt)) +
  geom_histogram(binwidth = 1) +
  scale_x_continuous(breaks = seq(1880, 2010, 5)) +
  theme(axis.text.x = element_text(angle = 90, hjust = 1))

p2 = ggplot(val_train_Xy, aes(x = YearBuilt.fact)) +
  geom_bar()

grid.arrange(p1, p2)

x = 'YearBuilt.fact'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)

y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "After_1986"  "Before_1945" "1945_1986"  

Age

Back to top.

It might make sense to transform year built into the age of the house when bought. The years of sales is a small range, but it may add a touch more predictive information, especially for the newer houses.

Normalize

It seems to have added a couple of outliers, but applying a square-root transformation better centers it and removes the outliers.

0s don’t indicate a missing feature, so I want to include them in the search for the best normalizer.

val_train_Xy = val_train_Xy %>%
  mutate('Age' = (YrSold - YearBuilt))

x = 'Age'

# Include 0s. Just recalculate rather than rewrite code.
num_feats = c(x)
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(-1)
)

summary(val_train_Xy[x])
      Age        
 Min.   :  0.00  
 1st Qu.:  8.00  
 Median : 35.00  
 Mean   : 36.83  
 3rd Qu.: 55.00  
 Max.   :136.00  
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1,
  t_binw = 1/10
)
NULL

x = 'sqrt(Age)'
val_train_Xy = val_train_Xy %>%
  mutate('sqrt(Age)' = sqrt(Age))

# Include 0s. Just recalculate rather than rewrite code.
num_feats = c(x)
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(-1)
)

summary(val_train_Xy[x])
   sqrt(Age)     
 Min.   : 0.000  
 1st Qu.: 2.828  
 Median : 5.916  
 Mean   : 5.341  
 3rd Qu.: 7.416  
 Max.   :11.662  
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1/10,
  t_binw = 1/10
)
NULL

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

Correlations

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('YearBuilt', 'Age', 'sqrt(Age)')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
   YearBuilt              Age            sqrt(Age)       
 Min.   :0.0009914   Min.   :0.00325   Min.   :0.000536  
 1st Qu.:0.0534053   1st Qu.:0.06282   1st Qu.:0.081377  
 Median :0.1616352   Median :0.15871   Median :0.165492  
 Mean   :0.2334731   Mean   :0.23488   Mean   :0.248951  
 3rd Qu.:0.3684433   3rd Qu.:0.36838   3rd Qu.:0.359191  
 Max.   :0.8301510   Max.   :0.82972   Max.   :0.847412  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')

y_lst = c('log(SalePrice)', 'Win(log(SalePrice))', 'GarageArea', 'GarageCars',
          'EnclosedPorch', 'OpenPorchSF', 'OverallQual_int', 'OverallCond_int')
plot_scat_pairs(df = val_train_Xy, x = x, y_lst = y_lst)
NULL

There’s a small increase in linear correlation of Age to transformed SalePrice from YearBuilt (0.59 to 0.62). Correlations to some measures of size strengthened. The correlation to quality rose quite a bit (from 0.59 to 0.65) while the correlation to condition only rose slightly (0.36 to 0.37), remaining weak.

With YearBuilt, there’s still a polynomial appearance to the plot against log(SalePrice), but that’s smoothed out with sqrt(Age). YearRemodAdd may add clarity.

The concentration of average-condition houses among the youngest is still puzzling. It’s as if a few years of aging tends to improve the condition of a house. Perhaps owners tend to add cosmetic value in the first years. Maybe assessors use the youngest houses as the anchor by which to assess all others. Many of these houses were unfinished, which explains some but not all of this.

ggplot(
  val_train_Xy,
  aes(
    x = .data[['sqrt(Age)']],
    y = OverallCond_int,
    shape = SaleCondition,
    color = SaleCondition
  )
) +
  geom_jitter()

val_train_Xy = select(val_train_Xy, -c('Age'))

YearRemodAdd

Back to top.

x = 'YearRemodAdd'
summary(val_train_Xy[x])
  YearRemodAdd 
 Min.   :1950  
 1st Qu.:1966  
 Median :1994  
 Mean   :1985  
 3rd Qu.:2004  
 Max.   :2010  
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1,
  t_binw = 1
)
NULL

There was a curiously large number of remodels in 1950, about 80 remodels that year compared to the peak of about 50 in the 2000s. These were all built before 1950, and no house has an earlier remodel year than 1950, and some houses built before 1950 have remodel years later than 1950.

I’ll treat houses with a 1950 remodel as if they have not had a remodel; they may have had an earlier remodel, but I’m guessing that those older remodels are of little added value at this point. Ames assessors may have had reason to bottom-code, but I’ll set remodel year to built year in years prior to 1950 for now and see if it helps or hinders analysis and prediction.

val_train_Xy = val_train_Xy %>%
  mutate(YearRemodAdd.uncode = ifelse(YearRemodAdd == 1950, YearBuilt, YearRemodAdd))
x = 'YearRemodAdd.uncode'
# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 YearRemodAdd.uncode
 Min.   :1880       
 1st Qu.:1966       
 Median :1994       
 Mean   :1982       
 3rd Qu.:2004       
 Max.   :2010       
# sum_and_trans_cont(
#   data = val_train_Xy,
#   x = x,
#   func = best_normalizers[[x]]$best_func$func,
#   func_name = best_normalizers[[x]]$best_func$name,
#   x_binw = 1,
#   t_binw = 1
# )
ggplot(val_train_Xy, aes(x = .data[[x]])) +
  geom_histogram(binwidth = 1)

Correlations

This only added outliers and slightly weakened the correlation to SalePrice.

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('YearBuilt', 'YearRemodAdd', 'YearRemodAdd.uncode')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
   YearBuilt          YearRemodAdd      YearRemodAdd.uncode
 Min.   :0.0009914   Min.   :0.004039   Min.   :0.001117   
 1st Qu.:0.0534053   1st Qu.:0.055532   1st Qu.:0.063792   
 Median :0.1616352   Median :0.152282   Median :0.145630   
 Mean   :0.2421409   Mean   :0.203658   Mean   :0.198553   
 3rd Qu.:0.3684433   3rd Qu.:0.294762   3rd Qu.:0.268835   
 Max.   :0.9624094   Max.   :0.654782   Max.   :0.657064   
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')

y_lst = c('log(SalePrice)')
plot_scat_pairs(df = val_train_Xy, x = x, y_lst = y_lst)
NULL

By Factors

y_lst = colnames(select(val_train_Xy, where(is.factor)))
for (y in y_lst) {
  plt = fenced_jbv(data = val_train_Xy, x = y, y = x) +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))
  print(plt)
}

val_train_Xy = select(val_train_Xy, -c('YearRemodAdd.uncode'))

Removing the records in which there is no remodel (i.e. YearRemodAdd == YearBuilt or 1950) adds clarity. 368 rows were removed for no remodel, and remodels really started being recorded in increasing numbers starting in the ’90s, maybe a little in the late ’80s.

ggplot(
  val_train_Xy,
  aes(x = ifelse(YearRemodAdd == YearBuilt, NA, YearRemodAdd)
  )
) +
  geom_histogram(binwidth = 1)

YearRemodAdd as Factor

Because there are so many houses without remodels, I’ll split it into a factor, a level for each decade with the year 1950 conveniently lumped in with NAs.

x = 'YearRemodAdd.fact'
val_train_Xy = val_train_Xy %>%
  mutate(
    YearRemodAdd.fact = factor(
      cut(
        ifelse(
          YearRemodAdd == YearBuilt | is.na(YearRemodAdd),
          1949,
          YearRemodAdd
        ),
        breaks = c(1949, 1950, 1960, 1970, 1980, 1990, 2000, 2010),
        labels = c('None', '50s', '60s', '70s', '80s', '90s', '00s')
      )
    )
  ) #%>%
  # select(-c('YearRemodAdd'))

y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "None" "00s"  "90s" 

RoofStyle

Back to top.

Most are Gable (563), and many are Hip (139). Flat, Gambrel, and Mansard are neglible.

x = 'RoofStyle'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "Gable" "Hip"  

RoofMatl

Back to top.

Vast majority are composition shingle (702). Can drop this feature.

summary(val_train_Xy$RoofMatl)
ClyTile CompShg Membran   Metal    Roll Tar&Grv WdShake WdShngl 
      1     705       1       0       0       4       1       3 
val_train_Xy = select(val_train_Xy, -c('RoofMatl'))

Exterior1st/2nd

Back to top.

Most popular class is vinyl (255 and 249), but wood, metal, and others represent significant classes. No ‘None’ in 2nd, so all houses in Ames have at least two types of siding?

x = 'Exterior1st'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "VinylSd" "MetalSd" "Wd Sdng" "HdBoard" "Plywood"
x = 'Exterior2nd'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "VinylSd" "MetalSd" "Wd Sdng" "HdBoard" "Plywood"

MasVnrType

Back to top.

Most have none (430), but plenty of brick (219) and stone (66). No cinderblock in the split.

x = 'MasVnrType'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "BrkFace" "None"    "Stone"  

MasVnrArea

Back to top.

Normalize

x = 'MasVnrArea'
summary(val_train_Xy[x])
   MasVnrArea    
 Min.   :   0.0  
 1st Qu.:   0.0  
 Median :   0.0  
 Mean   : 103.3  
 3rd Qu.: 166.5  
 Max.   :1129.0  
sum_and_trans_cont(
  data = val_train_Xy[val_train_Xy$MasVnrArea != 0, ],
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 10,
  t_binw = 0.25
)
NULL

x = 'cbrt(MasVnrArea)'
val_train_Xy = val_train_Xy %>%
  mutate('cbrt(MasVnrArea)' = MasVnrArea^(1/3))

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 cbrt(MasVnrArea)
 Min.   : 0.000  
 1st Qu.: 0.000  
 Median : 0.000  
 Mean   : 2.391  
 3rd Qu.: 5.501  
 Max.   :10.413  
sum_and_trans_cont(
  data = val_train_Xy[val_train_Xy$MasVnrArea != 0, ],
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 0.25,
  t_binw = 0.25
)
NULL

Winsorize

Because this variable’s 0s indicate a missing feature, we’re really concerned with Winsorizing non-zero values.

df = val_train_Xy[val_train_Xy$MasVnrArea != 0, ]

qqnorm(y = df$MasVnrArea, ylab = 'MasVnrArea')
qqline(y = df$MasVnrArea, ylab = 'MasVnrArea')


qqnorm(y = df[[x]], ylab = x)
qqline(y = df[[x]], ylab = x)


qqnorm(y = sqrt(sqrt(df$MasVnrArea)), ylab = 'sqrt(sqrt(MasVnrArea))')
qqline(y = sqrt(sqrt(df$MasVnrArea)), ylab = 'sqrt(sqrt(MasVnrArea))')


Win_sqrt_x = Winsorize(
  x = sqrt(sqrt(df$MasVnrArea)),
  probs = c(0.005, 0.995),
  na.rm = T
)

qqnorm(y = Win_sqrt_x, ylab = 'Win(sqrt(sqrt(MasVnrArea)))')
qqline(y = Win_sqrt_x, ylab = 'Win(sqrt(sqrt(MasVnrArea)))')


Win_cbrt_x = Winsorize(
  x = df$`cbrt(MasVnrArea)`,
  probs = c(0.005, 0.995),
  na.rm = T
)

qqnorm(y = Win_cbrt_x, ylab = 'Win(cbrt(MasVnrArea))')
qqline(y = Win_cbrt_x, ylab = 'Win(cbrt(MasVnrArea))')


Win_raw_x = Winsorize(
  x = df$MasVnrArea,
  probs = c(0.05, 0.95),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'Win(MasVnrArea)')
qqline(y = Win_raw_x, ylab = 'Win(MasVnrArea)')


print(shapiro.test(df$MasVnrArea))

    Shapiro-Wilk normality test

data:  df$MasVnrArea
W = 0.86407, p-value = 3.522e-15
print(shapiro.test(df$'cbrt(MasVnrArea)'))

    Shapiro-Wilk normality test

data:  df$"cbrt(MasVnrArea)"
W = 0.99498, p-value = 0.4769
print(shapiro.test(sqrt(sqrt(df$MasVnrArea))))

    Shapiro-Wilk normality test

data:  sqrt(sqrt(df$MasVnrArea))
W = 0.99153, p-value = 0.09952
print(shapiro.test(Win_sqrt_x))

    Shapiro-Wilk normality test

data:  Win_sqrt_x
W = 0.99525, p-value = 0.5281
print(shapiro.test(Win_cbrt_x))

    Shapiro-Wilk normality test

data:  Win_cbrt_x
W = 0.99576, p-value = 0.6327
print(shapiro.test(Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.90384, p-value = 1.551e-12
min_val = min(Win_cbrt_x)
max_val = max(Win_cbrt_x)
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(cbrt(MasVnrArea))' = ifelse(
      MasVnrArea == 0,
      0,
      Winsorize(
        x = `cbrt(MasVnrArea)`,
        # probs = c(0.005, 0.995),
        minval = min_val,
        maxval = max_val
      )
    )
  )

Correlations

x = 'Win(cbrt(MasVnrArea))'
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('MasVnrArea', 'cbrt(MasVnrArea)', x)

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    .data[[x]] != 0
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs (no 0s):")
[1] "Summary of absolute values of Pearson's Rs (no 0s):"
df = abs(df)
summary(abs(df))
   MasVnrArea      cbrt(MasVnrArea)  Win(cbrt(MasVnrArea))
 Min.   :0.01480   Min.   :0.00778   Min.   :0.005013     
 1st Qu.:0.06893   1st Qu.:0.07272   1st Qu.:0.071175     
 Median :0.10170   Median :0.13331   Median :0.134064     
 Mean   :0.14780   Mean   :0.15174   Mean   :0.151855     
 3rd Qu.:0.23594   3rd Qu.:0.23006   3rd Qu.:0.230854     
 Max.   :0.38455   Max.   :0.36920   Max.   :0.372665     
 NA's   :1         NA's   :1         NA's   :1            
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features (no 0s)')


df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
   MasVnrArea        cbrt(MasVnrArea)   Win(cbrt(MasVnrArea))
 Min.   :0.0009883   Min.   :0.007318   Min.   :0.007091     
 1st Qu.:0.0727252   1st Qu.:0.068257   1st Qu.:0.068430     
 Median :0.1386111   Median :0.152985   Median :0.153200     
 Mean   :0.1806451   Mean   :0.187874   Mean   :0.187854     
 3rd Qu.:0.3070987   3rd Qu.:0.314667   3rd Qu.:0.314633     
 Max.   :0.4212846   Max.   :0.430102   Max.   :0.430096     
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


y_lst = c('log(SalePrice)')
for (feat in x_lst) {
 plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst) 
}

This variable has a lot of zeros that indicate a missing feature that make it a candidate for binary encoding to aid linear regression. But, MasVnrType already encodes that in ‘None’. Because there’s a significant difference in price between MasVnrType levels, that should suffice. Also, leaving the 0s in improves the correlation to the target variable, so it may be moot.

Hard Code

# already hardcoded this one.

print(paste("min_val:", min_val))
[1] "min_val: 1.52019153849196"
print(paste("max_val:", max_val))
[1] "max_val: 10.278035603362"
ggplot(val_train_Xy, aes(x = .data[[x]])) +
  geom_histogram(binwidth = .2)

By Factors

y_lst = c('MasVnrType')
z = 'SalePrice.fact'
for (y in y_lst) {
  plt = fenced_jbv(
    data = val_train_Xy,
    x = y,
    y = 'cbrt(MasVnrArea)',
    jit_col = z,
    jit_alpha = 0.75,
    leg_lb = z,
    box_color = 'red'
  ) +
    theme(axis.text.x = element_text(angle = 45, hjust=1))
  print(plt)
}


ggplot(val_train_Xy, aes(x = `cbrt(MasVnrArea)`, y = `log(SalePrice)`)) +
  geom_point(alpha = 0.5) +
  geom_smooth(method = 'lm') +
  facet_wrap(vars(MasVnrType))

There are some houses with masonry footage but no type ascribed. I noticed this during the wrangling audit and decided to leave it for the modeling phase, maybe to impute the masonry type with caret using a select few variables as indicators or simply imputing the most common type (BrkFace), but maybe create a new level for those handful of undescribed masonry types.

ExterQual

Back to top.

Mostly average (440), many good (241).

x = 'ExterQual'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "Gd" "TA"

ExterCond

Back to top.

Greater majority average (621), still some good (74). May be worth keeping.

x = 'ExterCond'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
character(0)

Foundation

Back to top.

Evenly split between poured concrete and cinder block (317 and 302), but still 72 brick and tile.

x = 'Foundation'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "PConc"  "BrkTil" "CBlock"

Could drop Stone and Wood and make it an ordered factor to represent as ints in regression or to one-hot.

BsmtQual

Back to top.

Evenly split between average and good (313 and 303), but 63 excellent, and only 26 without basements. I’m having trouble imagining houses with basements and cinder block foundations.

x = 'BsmtQual'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "Gd" "TA" "Ex"

BsmtCond

Back to top.

Vast majority average (635).

x = 'BsmtCond'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
character(0)

BsmtExposure

Back to top.

Great majority (about 464) not exposed, but still some average (100), good (71), and minimal exposure (54).

x = 'BsmtExposure'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "No" "Av" "Mn" "Gd"

BsmtFinType1

Back to top.

211 are unfinished, 205 are top quality, and descending counts to low quality (34).

x = 'BsmtFinType1'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "GLQ" "Unf" "BLQ" "ALQ" "LwQ" "Rec"

Probably just one-hot GLQ, LwQ, and None for regression.

BsmtFinSF1

Back to top.

Normalize

x = 'BsmtFinSF1'
summary(val_train_Xy[x])
   BsmtFinSF1    
 Min.   :   0.0  
 1st Qu.:   0.0  
 Median : 375.0  
 Mean   : 446.5  
 3rd Qu.: 699.0  
 Max.   :5644.0  
sum_and_trans_cont(
  data = val_train_Xy[val_train_Xy$BsmtFinSF1 != 0, ],
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 50,
  t_binw = 0.25
)
NULL

x = 'cbrt(BsmtFinSF1)'
val_train_Xy = val_train_Xy %>%
  mutate('cbrt(BsmtFinSF1)' = BsmtFinSF1^(1/3))

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 cbrt(BsmtFinSF1)
 Min.   : 0.000  
 1st Qu.: 0.000  
 Median : 7.211  
 Mean   : 5.564  
 3rd Qu.: 8.875  
 Max.   :17.804  
sum_and_trans_cont(
  data = val_train_Xy[val_train_Xy$BsmtFinSF1 != 0, ],
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 0.25,
  t_binw = 0.25
)
NULL

ggplot(val_train_Xy, aes(x = .data[['cbrt(BsmtFinSF1)']])) +
  geom_histogram()

Correlations

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('BsmtFinSF1', 'cbrt(BsmtFinSF1)')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
   BsmtFinSF1       cbrt(BsmtFinSF1)  
 Min.   :0.006172   Min.   :0.000255  
 1st Qu.:0.079152   1st Qu.:0.069259  
 Median :0.218996   Median :0.115415  
 Mean   :0.220147   Mean   :0.157082  
 3rd Qu.:0.318490   3rd Qu.:0.196450  
 Max.   :0.644301   Max.   :0.643687  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


y_lst = c('log(SalePrice)')
plot_scat_pairs(df = val_train_Xy, x = x, y_lst = y_lst)
NULL

plot_scat_pairs(df = val_train_Xy, x = 'BsmtFinSF1', y_lst = y_lst)
NULL

This will be dropped in favor of a derivative variable, total basement sf, so no need to go any further.

val_train_Xy = select(val_train_Xy, -c('cbrt(BsmtFinSF1)'))

BsmtFinType2

Back to top.

Mostly unfinished (609) but 27 no basements. So, only one house in Ames with a basement doesn’t have a second basement?? This doesn’t seem right. It might be worth dropping this feature.

x = 'BsmtFinType2'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
character(0)

BsmtFinSF2

Back to top.

No need to look further as this will be dropped in favor of total bsmt sf.

BsmtUnfSF

Back to top.

Normalize

x = 'BsmtUnfSF'
summary(val_train_Xy[x])
   BsmtUnfSF     
 Min.   :   0.0  
 1st Qu.: 234.0  
 Median : 470.0  
 Mean   : 564.6  
 3rd Qu.: 795.5  
 Max.   :2046.0  
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 50,
  t_binw = 0.25
)
NULL

x = 'cbrt(BsmtUnfSF)'
val_train_Xy = val_train_Xy %>%
  mutate('cbrt(BsmtUnfSF)' = BsmtUnfSF^(1/3))

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 cbrt(BsmtUnfSF) 
 Min.   : 0.000  
 1st Qu.: 6.162  
 Median : 7.775  
 Mean   : 7.402  
 3rd Qu.: 9.266  
 Max.   :12.695  
sum_and_trans_cont(
  data = filter(val_train_Xy, BsmtUnfSF != 0),
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 0.25,
  t_binw = 0.25
)
NULL

Winsorize

Because this variable’s 0s indicate a missing basement, just Winsorizing non-zero values.

df = val_train_Xy[val_train_Xy$BsmtUnfSF != 0, ]

qqnorm(y = df$BsmtUnfSF, ylab = 'BsmtUnfSF')
qqline(y = df$BsmtUnfSF, ylab = 'BsmtUnfSF')


qqnorm(y = df[[x]], ylab = x)
qqline(y = df[[x]], ylab = x)


Win_cbrt_x = Winsorize(
  x = df[[x]],
  probs = c(0.05, 0.95),
  na.rm = T
)

qqnorm(y = Win_cbrt_x, ylab = 'Win(cbrt(BsmtUnfSF)')
qqline(y = Win_cbrt_x, ylab = 'Win(cbrt(BsmtUnfSF)')


Win_raw_x = Winsorize(
  x = df$BsmtUnfSF,
  probs = c(0, 0.95),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'BsmtUnfSF')
qqline(y = Win_raw_x, ylab = 'BsmtUnfSF')


print(shapiro.test(x = df$BsmtUnfSF))

    Shapiro-Wilk normality test

data:  df$BsmtUnfSF
W = 0.92795, p-value < 2.2e-16
print(shapiro.test(x = df$`cbrt(BsmtUnfSF)`))

    Shapiro-Wilk normality test

data:  df$`cbrt(BsmtUnfSF)`
W = 0.993, p-value = 0.003542
print(shapiro.test(x = Win_cbrt_x))

    Shapiro-Wilk normality test

data:  Win_cbrt_x
W = 0.96978, p-value = 2.054e-10
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.93316, p-value < 2.2e-16

Correlations

x = 'cbrt(BsmtUnfSF)'
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('BsmtUnfSF', 'cbrt(BsmtUnfSF)')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
   BsmtUnfSF        cbrt(BsmtUnfSF)   
 Min.   :0.007563   Min.   :0.007861  
 1st Qu.:0.042330   1st Qu.:0.039367  
 Median :0.137369   Median :0.128333  
 Mean   :0.135120   Mean   :0.122909  
 3rd Qu.:0.180533   3rd Qu.:0.165133  
 Max.   :0.473352   Max.   :0.356293  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    .data[[x]] != 0
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs (no 0s):")
[1] "Summary of absolute values of Pearson's Rs (no 0s):"
df = abs(df)
summary(abs(df))
   BsmtUnfSF        cbrt(BsmtUnfSF)   
 Min.   :0.008363   Min.   :0.001348  
 1st Qu.:0.055197   1st Qu.:0.045389  
 Median :0.101256   Median :0.078388  
 Mean   :0.127480   Mean   :0.108878  
 3rd Qu.:0.156947   3rd Qu.:0.141344  
 Max.   :0.534817   Max.   :0.539121  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features (no 0s)')


y_lst = c('log(SalePrice)')
for (feat in x_lst) {
  plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst)
}

TotalBsmtSF

Back to top.

Normalize

x = 'TotalBsmtSF'
summary(val_train_Xy[x])
  TotalBsmtSF    
 Min.   :   0.0  
 1st Qu.: 793.5  
 Median : 981.0  
 Mean   :1058.1  
 3rd Qu.:1291.0  
 Max.   :6110.0  
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 50,
  t_binw = 1/20
)
NULL

x = 'log(TotalBsmtSF)'
val_train_Xy = val_train_Xy %>%
  mutate(
    'log(TotalBsmtSF)' = ifelse(
    TotalBsmtSF <= 0,
    0,
    log(TotalBsmtSF)
    )
  )

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 log(TotalBsmtSF)
 Min.   :0.000   
 1st Qu.:6.676   
 Median :6.889   
 Mean   :6.734   
 3rd Qu.:7.163   
 Max.   :8.718   
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1/20,
  t_binw = 1
)
NULL

x = 'square(log(TotalBsmtSF))'
val_train_Xy = val_train_Xy %>%
  mutate(
    'square(log(TotalBsmtSF))' = ifelse(
    TotalBsmtSF <= 0,
    0,
    log(TotalBsmtSF)^2
    )
  )

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 square(log(TotalBsmtSF))
 Min.   : 0.00           
 1st Qu.:44.58           
 Median :47.45           
 Mean   :46.77           
 3rd Qu.:51.31           
 Max.   :76.00           
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1,
  t_binw = 1
)
NULL

Winsorize

Just checking non-zero set.

df = val_train_Xy[val_train_Xy$TotalBsmtSF != 0, ]

qqnorm(y = df$TotalBsmtSF, ylab = 'TotalBsmtSF')
qqline(y = df$TotalBsmtSF, ylab = 'TotalBsmtSF')


qqnorm(y = df[[x]], ylab = x)
qqline(y = df[[x]], ylab = x)


Win_log_x_squared = Winsorize(
  x = df[[x]],
  probs = c(0.005, 0.995),
  na.rm = T
)

qqnorm(y = Win_log_x_squared, ylab = 'Win_log_x_squared')
qqline(y = Win_log_x_squared, ylab = 'Win_log_x_squared')


Win_raw_x = Winsorize(
  x = df$TotalBsmtSF,
  probs = c(0, 0.99),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'Win_raw_x')
qqline(y = Win_raw_x, ylab = 'Win_raw_x')


print(shapiro.test(x = df$TotalBsmtSF))

    Shapiro-Wilk normality test

data:  df$TotalBsmtSF
W = 0.84661, p-value < 2.2e-16
print(shapiro.test(x= df$`square(log(TotalBsmtSF))`))

    Shapiro-Wilk normality test

data:  df$`square(log(TotalBsmtSF))`
W = 0.98181, p-value = 1.344e-07
print(shapiro.test(x = Win_log_x_squared))

    Shapiro-Wilk normality test

data:  Win_log_x_squared
W = 0.99018, p-value = 0.0001361
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.9591, p-value = 5.387e-13
x = 'Win(square(log(TotalBsmtSF)))'

val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(square(log(TotalBsmtSF)))' = ifelse(
      TotalBsmtSF <= 0,
      0,
      Winsorize(
        x = log(TotalBsmtSF)^2,
        probs = c(0.005, 0.995),
        na.rm = T
      )
    )
  ) %>%
  mutate(
    'Win(log(TotalBsmtSF))' = ifelse(
      TotalBsmtSF <= 0,
      0,
      Winsorize(
        x = log(TotalBsmtSF),
        probs = c(0.005, 0.995),
        na.rm = T
      )
    )
  )

Correlations

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('TotalBsmtSF', 'log(TotalBsmtSF)', 'square(log(TotalBsmtSF))',
          'Win(square(log(TotalBsmtSF)))')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
  TotalBsmtSF       log(TotalBsmtSF)   square(log(TotalBsmtSF))
 Min.   :0.001736   Min.   :0.001862   Min.   :0.004075        
 1st Qu.:0.111764   1st Qu.:0.067788   1st Qu.:0.076938        
 Median :0.352060   Median :0.155643   Median :0.226709        
 Mean   :0.306997   Mean   :0.178743   Mean   :0.233972        
 3rd Qu.:0.404087   3rd Qu.:0.229931   3rd Qu.:0.300477        
 Max.   :0.823514   Max.   :0.999521   Max.   :0.965497        
 Win(square(log(TotalBsmtSF)))
 Min.   :0.003976             
 1st Qu.:0.076883             
 Median :0.226443             
 Mean   :0.233493             
 3rd Qu.:0.299516             
 Max.   :0.966195             
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    .data[[x]] != 0
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs (no 0s):")
[1] "Summary of absolute values of Pearson's Rs (no 0s):"
df = abs(df)
summary(abs(df))
  TotalBsmtSF       log(TotalBsmtSF)   square(log(TotalBsmtSF))
 Min.   :0.002517   Min.   :0.006178   Min.   :0.004722        
 1st Qu.:0.143176   1st Qu.:0.128517   1st Qu.:0.135745        
 Median :0.331959   Median :0.323649   Median :0.326473        
 Mean   :0.315899   Mean   :0.310116   Mean   :0.313815        
 3rd Qu.:0.407319   3rd Qu.:0.414363   3rd Qu.:0.418140        
 Max.   :0.903203   Max.   :0.994652   Max.   :0.990654        
 Win(square(log(TotalBsmtSF)))
 Min.   :0.005277             
 1st Qu.:0.136215             
 Median :0.327097             
 Mean   :0.314560             
 3rd Qu.:0.420007             
 Max.   :0.988335             
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features') +
  xlab(label = 'Subset with no 0s')


y_lst = c('log(SalePrice)')
for (feat in x_lst) {
  plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst)
}

Looks to be some clustering here, two or three groups. Maybe those with and without a second basement, maybe basement types. I think regression will suss out what information is needed, but it’s worth noting.

The raw feature actually correlates much better with the target variable when 0s are included. It might be worth keeping the raw features for the Lasso regression to weed through. Normalizing apart from 0s may or may not help other regressions; it would be ideal for some kind of combo regression that splits/clusters then finds a linear regression, which is sort of what I’m attempting by feeding the binary version of these features to Lasso (where not already encoded by a factor). Normalizing apart for 0s is not likely to help decision trees much, but may help KNN a little by pulling 0s farther away and outliers closer.

Hard Code

x = 'Win(square(log(TotalBsmtSF)))'

min_val = min(Win_log_x_squared)
max_val = max(Win_log_x_squared)
print(paste("min_val:", min_val))
[1] "min_val: 32.6597927030784"
print(paste("max_val:", max_val))
[1] "max_val: 60.3486636755867"
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(square(log(TotalBsmtSF)))' = ifelse(
      TotalBsmtSF == 0,
      0,
      Winsorize(
        log(TotalBsmtSF)^2,
        # probs = c(0.005, 0.995),
        minval = min_val,
        maxval = max_val
      )
    )
  ) %>%
  select(-c('Win(log(TotalBsmtSF))', 'log(TotalBsmtSF)'))

gg = ggplot(val_train_Xy, aes(x = .data[[x]]))
p1 = gg + geom_histogram(binwidth = 1)
p2 = gg + geom_boxplot(notch = T)
grid.arrange(p1, p2)

Binarize

I can create a binary for basement when I one-hot encode during modeling, but I want to be able to use it for exploration here.

val_train_Xy = val_train_Xy %>%
  mutate('Bsmt.bin' = factor(ifelse(TotalBsmtSF == 0, 0, 1), ordered = T))

x = 'Bsmt.bin'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 20)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "1" "0"
val_train_Xy = select(val_train_Xy, -c('Bsmt.bin'))

TotalBsmtFinSF

Back to top.

TotalBsmtSF - BsmtUnfSF

Normalize

x = 'TotalBsmtFinSF'
val_train_Xy = val_train_Xy %>%
  mutate('TotalBsmtFinSF' = TotalBsmtSF - BsmtUnfSF)

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 TotalBsmtFinSF  
 Min.   :   0.0  
 1st Qu.:   0.0  
 Median : 456.0  
 Mean   : 493.6  
 3rd Qu.: 786.0  
 Max.   :5644.0  
sum_and_trans_cont(
  data = val_train_Xy[val_train_Xy[[x]] != 0, ],
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 50,
  t_binw = 1
)
NULL

x = 'sqrt(TotalBsmtFinSF)'
val_train_Xy = val_train_Xy %>%
  mutate('sqrt(TotalBsmtFinSF)' = sqrt(TotalBsmtFinSF))

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 sqrt(TotalBsmtFinSF)
 Min.   : 0.00       
 1st Qu.: 0.00       
 Median :21.35       
 Mean   :17.39       
 3rd Qu.:28.04       
 Max.   :75.13       
sum_and_trans_cont(
  data = val_train_Xy[val_train_Xy[[x]] != 0, ],
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1,
  t_binw = 1
)
NULL

Winsorize

Just the non-zero set.

x = 'sqrt(TotalBsmtFinSF)'
df = val_train_Xy[val_train_Xy$TotalBsmtFinSF != 0, ]

qqnorm(y = df$TotalBsmtFinSF, ylab = 'TotalBsmtFinSF')
qqline(y = df$TotalBsmtFinSF, ylab = 'TotalBsmtFinSF')


qqnorm(y = df[[x]], ylab = x)
qqline(y = df[[x]], ylab = x)



Win_sqrt_x = Winsorize(
  x = df[[x]],
  probs = c(0.03, 0.995),
  na.rm = T
)

qqnorm(y = Win_sqrt_x, ylab = 'Win_sqrt_x')
qqline(y = Win_sqrt_x, ylab = 'Win_sqrt_x')



Win_raw_x = Winsorize(
  x = df$TotalBsmtFinSF,
  probs = c(0.001, 0.99),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'Win(TotalBsmtFinSF)')
qqline(y = Win_raw_x, ylab = 'Win(TotalBsmtFinSF)')



print(shapiro.test(x = df$TotalBsmtFinSF))

    Shapiro-Wilk normality test

data:  df$TotalBsmtFinSF
W = 0.83537, p-value < 2.2e-16
print(shapiro.test(x = df$`sqrt(TotalBsmtFinSF)`))

    Shapiro-Wilk normality test

data:  df$`sqrt(TotalBsmtFinSF)`
W = 0.97064, p-value = 3.291e-08
print(shapiro.test(x = Win_sqrt_x))

    Shapiro-Wilk normality test

data:  Win_sqrt_x
W = 0.99214, p-value = 0.01256
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.97166, p-value = 5.265e-08
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(sqrt(TotalBsmtFinSF))' = ifelse(
      TotalBsmtFinSF == 0,
      0,
      Winsorize(
        x = sqrt(TotalBsmtFinSF),
        # probs = c(0.01, 0.995),
        minval = min(Win_sqrt_x),
        maxval = max(Win_sqrt_x)
      )
    )
  ) %>%
  mutate(
    'Win(TotalBsmtFinSF)' = ifelse(
      TotalBsmtFinSF == 0,
      0,
      Winsorize(
        x = TotalBsmtFinSF,
        # probs = c(0.001, 0.99)
        minval = min(Win_raw_x),
        maxval = max(Win_raw_x)
      )
    )
  )

x = 'Win(sqrt(TotalBsmtFinSF))'

Correlations

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('TotalBsmtFinSF', 'sqrt(TotalBsmtFinSF)', x)

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
 TotalBsmtFinSF      sqrt(TotalBsmtFinSF) Win(sqrt(TotalBsmtFinSF))
 Min.   :0.0004767   Min.   :0.007371     Min.   :0.008608         
 1st Qu.:0.0941765   1st Qu.:0.093248     1st Qu.:0.098192         
 Median :0.2387013   Median :0.166897     Median :0.155155         
 Mean   :0.2619773   Mean   :0.222351     Mean   :0.218137         
 3rd Qu.:0.3211208   3rd Qu.:0.277837     3rd Qu.:0.277832         
 Max.   :0.9567699   Max.   :0.957414     Max.   :0.955968         
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    .data[[x]] != 0
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs (no 0s):")
[1] "Summary of absolute values of Pearson's Rs (no 0s):"
df = abs(df)
summary(abs(df))
 TotalBsmtFinSF     sqrt(TotalBsmtFinSF) Win(sqrt(TotalBsmtFinSF))
 Min.   :0.002897   Min.   :0.0002521    Min.   :0.0187           
 1st Qu.:0.112971   1st Qu.:0.1000694    1st Qu.:0.1153           
 Median :0.274222   Median :0.2348650    Median :0.2413           
 Mean   :0.295891   Mean   :0.2677646    Mean   :0.2713           
 3rd Qu.:0.388785   3rd Qu.:0.3526043    3rd Qu.:0.3546           
 Max.   :0.917361   Max.   :0.9644098    Max.   :0.9887           
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features (no 0s)')


y_lst = c('log(SalePrice)')
for (feat in x_lst) {
  plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst)
}

Hard Code

x = 'Win(sqrt(TotalBsmtFinSF))'

min_val = min(Win_sqrt_x)
max_val = max(Win_sqrt_x)
print(paste("min_val:", min_val))
[1] "min_val: 10.1234508028247"
print(paste("max_val:", max_val))
[1] "max_val: 46.3884140769296"
# Already hard coded above.
val_train_Xy = select(val_train_Xy, -c('Win(TotalBsmtFinSF)'))

gg = ggplot(val_train_Xy, aes(x = .data[[x]]))
p1 = gg + geom_histogram(binwidth = 1)
p2 = gg + geom_boxplot(notch = T)
grid.arrange(p1, p2)

Heating

Back to top.

Mostly gas forced air (GasA, 697). Can probably drop this.

summary(val_train_Xy$Heating)
 None Floor  GasA  GasW  Grav  OthW  Wall 
    0     0   702     7     2     1     3 
val_train_Xy = select(val_train_Xy, -c('Heating'))

HeatingQC

Back to top.

Mostly excellent (360), then interestingly more average (211) than good (117). Enough variance to keep, but not normal.

x = 'HeatingQC'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "Ex" "TA" "Gd"

CentralAir

Back to top.

662 yes, 53 no. Probably drop this one.

summary(val_train_Xy$CentralAir)
  N   Y 
 39 676 
val_train_Xy = select(val_train_Xy, -c('CentralAir'))

Electrical

Back to top.

655 standard circuit breaker and Romex. Probably drop, but there are none mixed, so it might be worth making it an ordered factor if keeping.

summary(val_train_Xy$Electrical)
 None SBrkr FuseA FuseF FuseP   Mix 
    0   650    49    14     1     1 
val_train_Xy = select(val_train_Xy, -c('Electrical'))

X1stFlrSF

Back to top.

Dropping as component of GrLivArea.

summary(val_train_Xy$X1stFlrSF)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    334     881    1086    1167    1392    4692 
val_train_Xy = select(val_train_Xy, -c('X1stFlrSF'))

X2ndFlrSF

Back to top.

Dropping as component of GrLivArea. The presence of a second floor is encoded in HouseStyle, but it’s also convoluted between a few levels. I’ll make a binary out of it and keep that.

Binarize

val_train_Xy = val_train_Xy %>%
  mutate('X2ndFlr.bin' = ifelse(X2ndFlrSF <= 0, 0, 1)) %>%
  mutate('X2ndFlr.bin.fact' = factor(X2ndFlr.bin, ordered = T))

x = 'X2ndFlr.bin'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
x = 'X2ndFlr.bin.fact'
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "1" "0"
val_train_Xy = select(val_train_Xy, -c('X2ndFlr.bin.fact'))

LowQualFinSF

Back to top.

702 0s. Drop this feature, though it would be a useful modifier of GrLivArea as a detracting component of it.

summary(val_train_Xy$LowQualFinSF)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   0.000   0.000   4.846   0.000 528.000 
val_train_Xy = select(val_train_Xy, -c('LowQualFinSF'))

GrLivArea

Back to top.

Normalize

As this is polymodal to the number of floors, I’ll start by faceting before transforming.

ggplot(val_train_Xy, aes(x = GrLivArea)) +
  geom_histogram(binwidth = 50) +
  facet_wrap(facets = vars(X2ndFlr.bin), ncol = 1)

x = 'GrLivArea'
summary(val_train_Xy[x])
   GrLivArea   
 Min.   : 334  
 1st Qu.:1129  
 Median :1456  
 Mean   :1515  
 3rd Qu.:1791  
 Max.   :5642  
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 50,
  t_binw = .1
)
NULL

x = 'log2(GrLivArea)'

val_train_Xy = val_train_Xy %>%
  mutate('log2(GrLivArea)' = log2(GrLivArea))

ggplot(val_train_Xy, aes(x = .data[[x]])) +
  geom_histogram(binwidth = .1) +
  facet_wrap(facets = vars(X2ndFlr.bin), ncol = 1)


# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 log2(GrLivArea) 
 Min.   : 8.384  
 1st Qu.:10.141  
 Median :10.508  
 Mean   :10.486  
 3rd Qu.:10.807  
 Max.   :12.462  
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = .1,
  t_binw = 1
)
NULL

x = 'square(log2(GrLivArea))'

val_train_Xy = val_train_Xy %>%
  mutate('square(log2(GrLivArea))' = log2(GrLivArea)^2)

ggplot(val_train_Xy, aes(x = .data[[x]])) +
  geom_histogram(binwidth = 1) +
  facet_wrap(facets = vars(X2ndFlr.bin), ncol = 1)


# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 square(log2(GrLivArea))
 Min.   : 70.29         
 1st Qu.:102.84         
 Median :110.41         
 Mean   :110.18         
 3rd Qu.:116.78         
 Max.   :155.30         
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1,
  t_binw = 1
)
NULL

ggplot(val_train_Xy, aes(x = `square(log2(GrLivArea))`)) +
  geom_histogram(binwidth = 1) +
  facet_wrap(facets = vars(.data$X2ndFlr.bin), ncol = 1)


ggplot(val_train_Xy, aes(x = `square(log2(GrLivArea))`)) +
  geom_histogram(binwidth = 1) +
  facet_grid(
    cols = vars(.data$X2ndFlr.bin),
    rows = vars(.data$YearBuilt.fact)
  )

It looks like there might be more polymodality, particularly among mid-century builds, but I’ll leave it here. The transformations seem to have better normalized both the full variable and its subsets by storey and age. Winsorization should help both too.

Winsorize

x = 'square(log2(GrLivArea))'

qqnorm(y = val_train_Xy$GrLivArea, ylab = 'GrLivArea')
qqline(y = val_train_Xy$GrLivArea, ylab = 'GrLivArea')


qqnorm(y = val_train_Xy$`log2(GrLivArea)`, ylab = 'log2(GrLivArea)')
qqline(y = val_train_Xy$`log2(GrLivArea)`, ylab = 'log2(GrLivArea)')


qqnorm(y = val_train_Xy[[x]], ylab = x)
qqline(y = val_train_Xy[[x]], ylab = x)


Win_log2_x_squared = Winsorize(
  x = val_train_Xy[[x]],
  probs = c(0.002, 0.998),
  na.rm = T
)

qqnorm(y = Win_log2_x_squared, ylab = 'Win_log2_x_squared')
qqline(y = Win_log2_x_squared, ylab = 'Win_log2_x_squared')


Win_log2_x = Winsorize(
  x = val_train_Xy$`log2(GrLivArea)`,
  probs = c(0.002, 0.998),
  na.rm = T
)

qqnorm(y = Win_log2_x, ylab = 'Win_log2_x')
qqline(y = Win_log2_x, ylab = 'Win_log2_x')


Win_raw_x = Winsorize(
  x = val_train_Xy$GrLivArea,
  probs = c(0, 0.95),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'Win_raw_x')
qqline(y = Win_raw_x, ylab = 'Win_raw_x')


print(shapiro.test(x = val_train_Xy$GrLivArea))

    Shapiro-Wilk normality test

data:  val_train_Xy$GrLivArea
W = 0.92124, p-value < 2.2e-16
print(shapiro.test(x = val_train_Xy$`log2(GrLivArea)`))

    Shapiro-Wilk normality test

data:  val_train_Xy$`log2(GrLivArea)`
W = 0.99301, p-value = 0.002004
print(shapiro.test(x = val_train_Xy[['square(log2(GrLivArea))']]))

    Shapiro-Wilk normality test

data:  val_train_Xy[["square(log2(GrLivArea))"]]
W = 0.99284, p-value = 0.001655
print(shapiro.test(x = Win_log2_x_squared))

    Shapiro-Wilk normality test

data:  Win_log2_x_squared
W = 0.99603, p-value = 0.06738
print(shapiro.test(x = Win_log2_x))

    Shapiro-Wilk normality test

data:  Win_log2_x
W = 0.99606, p-value = 0.06957
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.97112, p-value = 1.136e-10

Winsorizing the log2 better normalized than squaring it, but Winsorizing the squared log2 is close, too. May be overfit to add the square.

val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(log2(GrLivArea))' = Winsorize(
      ifelse(GrLivArea <= 0, 0, log2(GrLivArea)),
      probs = c(0.002, 0.998),
      na.rm = T
    )
  ) %>%
  mutate(
    'Win(square(log2(GrLivArea)))' = Winsorize(
      ifelse(GrLivArea <= 0, 0, log2(GrLivArea)^2),
      probs = c(0.002, 0.998),
      na.rm = T
    )
  )

Correlations

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('GrLivArea', 'log2(GrLivArea)', 'square(log2(GrLivArea))', 'Win(log2(GrLivArea))', 'Win(square(log2(GrLivArea)))')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
   GrLivArea        log2(GrLivArea)    square(log2(GrLivArea))
 Min.   :0.004074   Min.   :0.007031   Min.   :0.005632       
 1st Qu.:0.170723   1st Qu.:0.138405   1st Qu.:0.144044       
 Median :0.300589   Median :0.308047   Median :0.308343       
 Mean   :0.320766   Mean   :0.319385   Mean   :0.320768       
 3rd Qu.:0.444557   3rd Qu.:0.439274   3rd Qu.:0.442560       
 Max.   :0.829900   Max.   :0.826904   Max.   :0.831350       
 Win(log2(GrLivArea)) Win(square(log2(GrLivArea)))
 Min.   :0.007115     Min.   :0.005648            
 1st Qu.:0.127613     1st Qu.:0.128787            
 Median :0.306231     Median :0.304675            
 Mean   :0.317085     Mean   :0.318136            
 3rd Qu.:0.428208     3rd Qu.:0.429922            
 Max.   :0.827504     Max.   :0.831948            
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


y_lst = c('log(SalePrice)')
for (feat in x_lst) {
  plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst)
}

Hard Code

The Winsorized double transformation may be slightly overfitting and overcomputing, but it’s slightly (likely insignificantly) more normal and correlated to SalePrice than the Winsorized single transformation. I’ll go with it despite common sense, just for fun.

x = 'Win(square(log2(GrLivArea)))'

min_val = min(Win_log2_x_squared)
max_val = max(Win_log2_x_squared)
print(paste("min_val:", min_val))
[1] "min_val: 78.8827556705776"
print(paste("max_val:", max_val))
[1] "max_val: 143.236548514549"
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(square(log2(GrLivArea)))' = Winsorize(
      ifelse(GrLivArea <= 0, 0, log2(GrLivArea)^2),
      # probs = c(0.002, 0.998),
      # na.rm = T
      minval = min_val,
      maxval = max_val
    )
  ) %>%
  select(-c('log2(GrLivArea)', 'Win(log2(GrLivArea))'))

gg = ggplot(val_train_Xy, aes(x = .data[[x]]))
p1 = gg + geom_histogram(binwidth = 1)
p2 = gg + geom_boxplot(notch = T)
grid.arrange(p1, p2)

BsmtFullBath

Back to top.

To contribute to full count.

BsmtHalfBath

Back to top.

To contribute to full count.

FullBath

Back to top.

To contribute to full count.

HalfBath

Back to top.

To contribute to full count.

TotBaths

Back to top.

I could make two features, total baths above grade and total basement baths, but I’ll keep it simple.

Normalize

val_train_Xy = val_train_Xy %>%
  mutate(TotBaths = FullBath + BsmtFullBath + 0.5*HalfBath + 0.5*BsmtHalfBath)

x = 'TotBaths'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL


ggplot(val_train_Xy, aes(x = factor(TotBaths), y = log(SalePrice))
) +
  geom_jitter(alpha = 0.5, aes(color = factor(BsmtFullBath + BsmtHalfBath))) +
  geom_boxplot(
    notch = T,
    notchwidth = .1,
    varwidth = T,
    alpha = 0,
    color = 'blue'
  ) +
  geom_violin(alpha = 0) +
  geom_line(
    stat = 'summary',
    fun = quantile,
    fun.args = list(probs = .9),
    linetype = 2, aes(group = 1)
  ) +
  geom_line(stat = 'summary', fun = mean, mapping = aes(group = 1)) +
  geom_line(
    stat = 'summary',
    fun = quantile,
    fun.args = list(probs = .1),
    linetype = 2, aes(group = 1)
  ) +
  xlab(label = 'TotBaths') + ylab(label = 'log(SalePrice)')

Basement bathrooms don’t seem to add much value when there aren’t many bathrooms altogether.

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "3.5" "2"   "3"   "1"   "1.5" "2.5"
# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
    TotBaths    
 Min.   :1.000  
 1st Qu.:2.000  
 Median :2.000  
 Mean   :2.217  
 3rd Qu.:2.500  
 Max.   :6.000  
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 0.5,
  t_binw = 0.1
)
NULL

x = 'sqrt(TotBaths)'
val_train_Xy = val_train_Xy %>%
  mutate('sqrt(TotBaths)' = sqrt(TotBaths))

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 sqrt(TotBaths) 
 Min.   :1.000  
 1st Qu.:1.414  
 Median :1.414  
 Mean   :1.464  
 3rd Qu.:1.581  
 Max.   :2.449  
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = .1,
  t_binw = .1
)
NULL

Winsorize

x = 'sqrt(TotBaths)'

qqnorm(y = val_train_Xy$TotBaths, ylab = 'TotBaths')
qqline(y = val_train_Xy$TotBaths, ylab = 'TotBaths')


qqnorm(y = val_train_Xy[[x]], ylab = x)
qqline(y = val_train_Xy[[x]], ylab = x)


Win_sqrt_x = Winsorize(
  x = val_train_Xy[[x]],
  probs = c(0.0, 0.999),
  na.rm = T
)

qqnorm(y = Win_sqrt_x, ylab = 'Win_sqrt_x')
qqline(y = Win_sqrt_x, ylab = 'Win_sqrt_x')


Win_raw_x = Winsorize(
  x = val_train_Xy$TotBaths,
  probs = c(0, 0.998),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'Win_raw_x')
qqline(y = Win_raw_x, ylab = 'Win_raw_x')


print(shapiro.test(x = val_train_Xy$TotBaths))

    Shapiro-Wilk normality test

data:  val_train_Xy$TotBaths
W = 0.92874, p-value < 2.2e-16
print(shapiro.test(x = val_train_Xy$`sqrt(TotBaths)`))

    Shapiro-Wilk normality test

data:  val_train_Xy$`sqrt(TotBaths)`
W = 0.9241, p-value < 2.2e-16
print(shapiro.test(x = Win_sqrt_x))

    Shapiro-Wilk normality test

data:  Win_sqrt_x
W = 0.92391, p-value < 2.2e-16
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.93136, p-value < 2.2e-16

Maybe just lightly top-coding the raw variable is best.

val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(sqrt(TotBaths))' = Winsorize(
      sqrt(TotBaths),
      probs = c(0, 0.999),
      na.rm = T
    )
  ) %>%
  mutate(
    'Win(TotBaths)' = Winsorize(
      TotBaths,
      probs = c(0, 0.998),
      na.rm = T
    )
  )

Correlations

x = 'Win(TotBaths)'
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('TotBaths', 'sqrt(TotBaths)', 'Win(sqrt(TotBaths))', 'Win(TotBaths)')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
    TotBaths        sqrt(TotBaths)     Win(sqrt(TotBaths))
 Min.   :0.004047   Min.   :0.001146   Min.   :0.0004721  
 1st Qu.:0.157148   1st Qu.:0.155984   1st Qu.:0.1568243  
 Median :0.329253   Median :0.331356   Median :0.3319365  
 Mean   :0.329428   Mean   :0.328623   Mean   :0.3294207  
 3rd Qu.:0.487119   3rd Qu.:0.483214   3rd Qu.:0.4844607  
 Max.   :0.687363   Max.   :0.687107   Max.   :0.6868680  
 Win(TotBaths)     
 Min.   :0.002175  
 1st Qu.:0.160011  
 Median :0.331592  
 Mean   :0.332448  
 3rd Qu.:0.493532  
 Max.   :0.688339  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


y_lst = c('log(SalePrice)')
for (feat in x_lst) {
  plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst)
}

Hard Code

x = 'Win(TotBaths)'

min_val = min(Win_raw_x)
max_val = max(Win_raw_x)
print(paste("min_val:", min_val))
[1] "min_val: 1"
print(paste("max_val:", max_val))
[1] "max_val: 4.786"
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(TotBaths)' = Winsorize(
      FullBath + BsmtFullBath + 0.5*HalfBath + 0.5*BsmtHalfBath,
      # probs = c(0.002, 0.998),
      # na.rm = T
      minval = min_val,
      maxval = max_val
    )
  ) %>%
  select(-c('sqrt(TotBaths)', 'Win(sqrt(TotBaths))'))

gg = ggplot(val_train_Xy, aes(x = .data[[x]]))
p1 = gg + geom_histogram(binwidth = 0.5)
p2 = gg + geom_boxplot(notch = T)
grid.arrange(p1, p2)

BedroomAbvGr

Back to top.

Normalize

x = 'BedroomAbvGr'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'

df = select(val_train_Xy, c('log(SalePrice)', 'BedroomAbvGr'))
df$BedroomAbvGr.fact = factor(df$BedroomAbvGr)
sum_and_trans_fact(data = df, x = 'BedroomAbvGr.fact', y = y)
NULL

p_vals = get_signif_levels(
  data = df,
  x = 'BedroomAbvGr.fact',
  z = y,
  min_n = 30
)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "3" "2" "4"
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('BedroomAbvGr')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
  BedroomAbvGr     
 Min.   :0.004635  
 1st Qu.:0.041301  
 Median :0.090023  
 Mean   :0.151626  
 3rd Qu.:0.209621  
 Max.   :0.654507  
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 0.5,
  t_binw = 0.1
)
NULL

x = 'sqrt(BedroomAbvGr)'
val_train_Xy = val_train_Xy %>%
  mutate('sqrt(BedroomAbvGr)' = sqrt(BedroomAbvGr))

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 sqrt(BedroomAbvGr)
 Min.   :1.000     
 1st Qu.:1.414     
 Median :1.732     
 Mean   :1.682     
 3rd Qu.:1.732     
 Max.   :2.828     
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = .1,
  t_binw = .1
)
NULL

Winsorize

x = 'sqrt(BedroomAbvGr)'

qqnorm(y = val_train_Xy$BedroomAbvGr, ylab = 'BedroomAbvGr')
qqline(y = val_train_Xy$BedroomAbvGr, ylab = 'BedroomAbvGr')


qqnorm(y = val_train_Xy[[x]], ylab = x)
qqline(y = val_train_Xy[[x]], ylab = x)


Win_sqrt_x = Winsorize(
  x = val_train_Xy[[x]],
  probs = c(0, 0.999),
  na.rm = T
)

qqnorm(y = Win_sqrt_x, ylab = 'Win_sqrt_x')
qqline(y = Win_sqrt_x, ylab = 'Win_sqrt_x')


Win_raw_x = Winsorize(
  x = val_train_Xy$BedroomAbvGr,
  probs = c(0, 0.995),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'Win_raw_x')
qqline(y = Win_raw_x, ylab = 'Win_raw_x')


print(shapiro.test(x = val_train_Xy$BedroomAbvGr))

    Shapiro-Wilk normality test

data:  val_train_Xy$BedroomAbvGr
W = 0.8457, p-value < 2.2e-16
print(shapiro.test(x = val_train_Xy$`sqrt(BedroomAbvGr)`))

    Shapiro-Wilk normality test

data:  val_train_Xy$`sqrt(BedroomAbvGr)`
W = 0.84731, p-value < 2.2e-16
print(shapiro.test(x = Win_sqrt_x))

    Shapiro-Wilk normality test

data:  Win_sqrt_x
W = 0.8486, p-value < 2.2e-16
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.8574, p-value < 2.2e-16

Maybe just lightly Winsorizing the raw variable is best.

val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(sqrt(BedroomAbvGr))' = Winsorize(
      sqrt(BedroomAbvGr),
      probs = c(0, 0.999),
      na.rm = T
    )
  ) %>%
  mutate(
    'Win(BedroomAbvGr)' = Winsorize(
      BedroomAbvGr,
      probs = c(0, 0.995),
      na.rm = T
    )
  )

Correlations

x = 'Win(BedroomAbvGr)'
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('BedroomAbvGr', 'sqrt(BedroomAbvGr)', 'Win(sqrt(BedroomAbvGr))', 'Win(BedroomAbvGr)')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
  BedroomAbvGr      sqrt(BedroomAbvGr) Win(sqrt(BedroomAbvGr))
 Min.   :0.004635   Min.   :0.001827   Min.   :0.002047       
 1st Qu.:0.041301   1st Qu.:0.044497   1st Qu.:0.044839       
 Median :0.090023   Median :0.094369   Median :0.094250       
 Mean   :0.151626   Mean   :0.150984   Mean   :0.151281       
 3rd Qu.:0.209621   3rd Qu.:0.216792   3rd Qu.:0.217943       
 Max.   :0.654507   Max.   :0.638464   Max.   :0.635445       
 Win(BedroomAbvGr) 
 Min.   :0.003767  
 1st Qu.:0.041751  
 Median :0.091144  
 Mean   :0.154296  
 3rd Qu.:0.219703  
 Max.   :0.649031  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


y_lst = c('log(SalePrice)')
for (feat in x_lst) {
  plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst)
}

Hard Code

x = 'Win(BedroomAbvGr)'

min_val = min(Win_raw_x)
max_val = max(Win_raw_x)
print(paste("min_val:", min_val))
[1] "min_val: 1"
print(paste("max_val:", max_val))
[1] "max_val: 5.42999999999995"
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(BedroomAbvGr)' = Winsorize(
      BedroomAbvGr,
      # probs = c(0.002, 0.998),
      # na.rm = T
      minval = min_val,
      maxval = max_val
    )
  ) %>%
  select(-c('sqrt(BedroomAbvGr)', 'Win(sqrt(BedroomAbvGr))'))

gg = ggplot(val_train_Xy, aes(x = .data[[x]]))
p1 = gg + geom_histogram(binwidth = 1)
p2 = gg + geom_boxplot(notch = T)
grid.arrange(p1, p2)

KitchenAbvGr

Back to top.

summary(val_train_Xy$KitchenAbvGr)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   1.000   1.000   1.041   1.000   3.000 
val_train_Xy = select(val_train_Xy, -c('KitchenAbvGr'))

KitchenQual

Back to top.

x = 'KitchenQual'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "Gd" "TA" "Ex"

TotRmsAbvGrd

Back to top.

x = 'TotRmsAbvGrd'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'

df = select(val_train_Xy, c('log(SalePrice)', 'TotRmsAbvGrd'))
df$TotRmsAbvGrd.fact = factor(df$TotRmsAbvGrd)
sum_and_trans_fact(data = df, x = 'TotRmsAbvGrd.fact', y = y)
NULL

p_vals = get_signif_levels(
  data = df,
  x = 'TotRmsAbvGrd.fact',
  z = y,
  min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "8" "5" "6" "4" "7" "9"
summary(val_train_Xy[x])
  TotRmsAbvGrd   
 Min.   : 2.000  
 1st Qu.: 5.000  
 Median : 6.000  
 Mean   : 6.543  
 3rd Qu.: 7.000  
 Max.   :14.000  
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1,
  t_binw = 1
)
NULL

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('TotRmsAbvGrd')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
  TotRmsAbvGrd    
 Min.   :0.00436  
 1st Qu.:0.10320  
 Median :0.22718  
 Mean   :0.28644  
 3rd Qu.:0.40813  
 Max.   :0.83195  

Winsorize

x = 'TotRmsAbvGrd'

qqnorm(y = val_train_Xy[[x]], ylab = x)
qqline(y = val_train_Xy[[x]], ylab = x)


Win_raw_x = Winsorize(
  x = val_train_Xy$TotRmsAbvGrd,
  probs = c(0, 0.975),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'Win(TotRmsAbvGrd)')
qqline(y = Win_raw_x, ylab = 'Win(TotRmsAbvGrd)')


print(shapiro.test(x = val_train_Xy$TotRmsAbvGrd))

    Shapiro-Wilk normality test

data:  val_train_Xy$TotRmsAbvGrd
W = 0.93616, p-value < 2.2e-16
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.94556, p-value = 1.508e-15
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(TotRmsAbvGrd)' = Winsorize(
      TotRmsAbvGrd,
      probs = c(0, 0.975),
      na.rm = T
    )
  )

Correlations

x = 'Win(TotRmsAbvGrd)'
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('TotRmsAbvGrd', 'Win(TotRmsAbvGrd)')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
  TotRmsAbvGrd     Win(TotRmsAbvGrd) 
 Min.   :0.00436   Min.   :0.006598  
 1st Qu.:0.10320   1st Qu.:0.092855  
 Median :0.22718   Median :0.224392  
 Mean   :0.28644   Mean   :0.286050  
 3rd Qu.:0.40813   3rd Qu.:0.414866  
 Max.   :0.83195   Max.   :0.833412  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


y_lst = c('log(SalePrice)')
for (feat in x_lst) {
  plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst)
}

Hard Code

x = 'Win(TotRmsAbvGrd)'

min_val = min(Win_raw_x)
max_val = max(Win_raw_x)
print(paste("min_val:", min_val))
[1] "min_val: 2"
print(paste("max_val:", max_val))
[1] "max_val: 10.15"
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(TotRmsAbvGrd)' = Winsorize(
      TotRmsAbvGrd,
      # probs = c(0, 0.975),
      # na.rm = T
      minval = min_val,
      maxval = max_val
    )
  )

gg = ggplot(val_train_Xy, aes(x = .data[[x]]))
p1 = gg + geom_histogram(binwidth = 1)
p2 = gg + geom_boxplot(notch = T)
grid.arrange(p1, p2)

Functional

Back to top.


x = 'Functional'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
character(0)

Fireplaces

Back to top.

x = 'Fireplaces'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)

val_train_Xy$Fireplaces.fact = factor(val_train_Xy$Fireplaces, ordered = T)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = 'Fireplaces.fact', y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = 'Fireplaces.fact', z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "0" "2" "1"
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('Fireplaces')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
   Fireplaces       
 Min.   :0.0001063  
 1st Qu.:0.1125161  
 Median :0.2436102  
 Mean   :0.2242305  
 3rd Qu.:0.3210328  
 Max.   :0.4867665  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


plot_scat_pairs(df = val_train_Xy, x = x, y_lst = c(y))
NULL

The variable needs more than three unique values in order to use my function to check for transformations, and 0 can’t be one of them in order to include log transformations. So, I’ll use Fireplaces + 1 to search for transformations.

val_train_Xy$Fireplaces_plus1 = val_train_Xy$Fireplaces + 1
x = 'Fireplaces_plus1'

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1,
  t_binw = 1
)
NULL

Winsorize

x = 'Fireplaces'

qqnorm(y = val_train_Xy[[x]], ylab = x)
qqline(y = val_train_Xy[[x]], ylab = x)


Win_raw_x = Winsorize(
  x = val_train_Xy$Fireplaces,
  probs = c(0, 0.999),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'Win_raw_x')
qqline(y = Win_raw_x, ylab = 'Win_raw_x')


print(shapiro.test(x = val_train_Xy$Fireplaces))

    Shapiro-Wilk normality test

data:  val_train_Xy$Fireplaces
W = 0.76773, p-value < 2.2e-16
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.76773, p-value < 2.2e-16

Winsorization doesn’t help.

Binarize

There’s no apparent meaningful difference in price between houses with 1 fireplace and those with 2 or 3. It’s significant (p < 0.1), but not apparently meaningful given the overlapping notches in the boxplot above, though I did not take Cohen’s d for effect size.

Binarization may condense the feature space without a lot of information loss. In fact, it improves the correlation to the target variable.

val_train_Xy = val_train_Xy %>%
  mutate(Fireplaces.bin = ifelse(Fireplaces == 0, 0, 1))

x = 'Fireplaces.bin'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)

val_train_Xy$Fireplaces.bin.fact = factor(val_train_Xy$Fireplaces.bin, ordered = T)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = 'Fireplaces.bin.fact', y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = 'Fireplaces.bin.fact', z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "0" "1"
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('Fireplaces', 'Fireplaces.bin')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
   Fireplaces        Fireplaces.bin    
 Min.   :0.0001063   Min.   :0.002832  
 1st Qu.:0.1163174   1st Qu.:0.138881  
 Median :0.2436660   Median :0.220999  
 Mean   :0.2380836   Mean   :0.237974  
 3rd Qu.:0.3215369   3rd Qu.:0.299826  
 Max.   :1.0000000   Max.   :0.886894  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')

val_train_Xy = select(
  val_train_Xy,
  -c('Fireplaces.fact', 'Fireplaces_plus1', 'Fireplaces.bin.fact',
     'Fireplaces')
)

FireplaceQu

Back to top.

x = 'FireplaceQu'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 29)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "None" "TA"   "Gd"  

One-hot encoding this FireplaceQu will include a binarized version (“None”:int[0,1]), so we can drop Fireplace.bin altogether, and Fireplace because it just adds noise. I’ll take care of that during modeling with caret.

GarageType

Back to top.

x = 'GarageType'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "Attchd"  "Detchd"  "None"    "BuiltIn"

GarageYrBlt

Back to top.

Similar distribution as year built for house. May not add additional information, but may be useful in interaction with type and finish. Could drop and use YearBuilt as proxy but would lose some info.

x = 'GarageYrBlt'
summary(val_train_Xy[x])
  GarageYrBlt  
 Min.   :1908  
 1st Qu.:1961  
 Median :1979  
 Mean   :1978  
 3rd Qu.:2002  
 Max.   :2010  
 NA's   :40    
# sum_and_trans_cont(
#   data = val_train_Xy,
#   x = x,
#   func = best_normalizers[[x]]$best_func$func,
#   func_name = best_normalizers[[x]]$best_func$name,
#   x_binw = 1,
#   t_binw = 1
# )

gg = ggplot(val_train_Xy, aes(x = GarageYrBlt))
p1 = gg + geom_histogram(binwidth = 1)
p2 = gg + geom_boxplot(notch = T)
grid.arrange(p1, p2)

Correlations

x = 'GarageYrBlt'
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c(x)

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
  GarageYrBlt      
 Min.   :0.006671  
 1st Qu.:0.057040  
 Median :0.177159  
 Mean   :0.235013  
 3rd Qu.:0.313315  
 Max.   :0.847412  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


y_lst = c('log(SalePrice)')
plot_scat_pairs(df = val_train_Xy, x = x, y_lst = y_lst)
NULL

“Controlling”

Even “controlling” for the year the house was built, GarageYrBlt doesn’t predict sale prices well, as seen in the next few plots. I’ll just drop it.

val_train_Xy = val_train_Xy %>%
  mutate(
    'GarBltLater' = factor(ifelse((GarageYrBlt - YearBuilt) <= 0, 0, 1))
  )

ggplot(val_train_Xy, aes(x = GarageYrBlt, y = YearBuilt)) +
  geom_jitter(
    alpha = 0.5,
    aes(
      color = SalePrice.fact
    ),
    position = position_jitter(w = 1, h = 1)
  )


fbv = fenced_jbv(
  data = val_train_Xy,
  x = 'GarBltLater',
  y = 'log(SalePrice)'
)
fbv


fbv +
  facet_wrap(facets = vars(YearBuilt.fact))


val_train_Xy$resids = lm(
  `log(SalePrice)` ~ `sqrt(Age)`,
  val_train_Xy
)$residuals

df = filter(
  val_train_Xy,
  GarageYrBlt - YearBuilt != 0
)

ggplot(df, aes(x = GarageYrBlt, y = resids)) +
  geom_point() +
  geom_smooth(method = 'lm') +
  ylab(label = 'log(SalePrice) ~ YearBuilt Residuals')


print(
  paste(
    "Correlation of GarageYRBlt to Age residual:",
    cor(df$GarageYrBlt, df$resids)
  )
)
[1] "Correlation of GarageYRBlt to Age residual: 0.0701692753019878"
val_train_Xy = select(val_train_Xy, -c('GarageYrBlt', 'GarBltLater'))

GarageFinish

Back to top.

x = 'GarageFinish'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "RFn"  "Unf"  "None" "Fin" 

GarageCars

Back to top.

x = 'GarageCars'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)

y = 'log(SalePrice)'
val_train_Xy$GarageCars.fact = factor(
  val_train_Xy$GarageCars,
  ordered = T
)
sum_and_trans_fact(data = val_train_Xy, x = 'GarageCars.fact', y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = 'GarageCars.fact', z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "2" "1" "3" "0"
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c(x)

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
   GarageCars      
 Min.   :0.005684  
 1st Qu.:0.162049  
 Median :0.279956  
 Mean   :0.293729  
 3rd Qu.:0.408933  
 Max.   :0.870258  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


plot_scat_pairs(df = val_train_Xy, x = x, y_lst = c(y))
NULL

val_train_Xy$GarageCars.plus1 = val_train_Xy$GarageCars + 1
x = 'GarageCars.plus1'

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1,
  t_binw = 1
)
NULL

Winsorize

x = 'GarageCars'

qqnorm(y = val_train_Xy[[x]], ylab = x)
qqline(y = val_train_Xy[[x]], ylab = x)


val_train_Xy$`Win(GarageCars)` = Winsorize(
  x = val_train_Xy[[x]],
  probs = c(0, 0.999),
  na.rm = T
)

qqnorm(y = val_train_Xy$`Win(GarageCars)`, ylab = 'Win_raw_x')
qqline(y = val_train_Xy$`Win(GarageCars)`, ylab = 'Win_raw_x')


print(shapiro.test(x = val_train_Xy[[x]]))

    Shapiro-Wilk normality test

data:  val_train_Xy[[x]]
W = 0.83694, p-value < 2.2e-16
print(shapiro.test(x = val_train_Xy$`Win(GarageCars)`))

    Shapiro-Wilk normality test

data:  val_train_Xy$`Win(GarageCars)`
W = 0.83446, p-value < 2.2e-16

Binarize

val_train_Xy = val_train_Xy %>%
  mutate(GarageCars.bin = ifelse(GarageCars == 0, 0, 1))

x = 'GarageCars.bin'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)

val_train_Xy$GarageCars.bin.fact =
  factor(val_train_Xy$GarageCars.bin, ordered = T)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = 'GarageCars.bin.fact', y = y)
NULL

p_vals = get_signif_levels(
  data = val_train_Xy, x = 'GarageCars.bin.fact', z = y, min_n = 30
)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "1" "0"
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('GarageCars', 'Win(GarageCars)', 'GarageCars.bin')

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
   GarageCars       Win(GarageCars)    GarageCars.bin    
 Min.   :0.005684   Min.   :0.005378   Min.   :0.005967  
 1st Qu.:0.163144   1st Qu.:0.163484   1st Qu.:0.051718  
 Median :0.282048   Median :0.283037   Median :0.090024  
 Mean   :0.306341   Mean   :0.307958   Mean   :0.118027  
 3rd Qu.:0.419101   3rd Qu.:0.421339   3rd Qu.:0.148698  
 Max.   :1.000000   Max.   :0.999361   Max.   :0.568588  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')

That didn’t seem to help.

GarageArea

Back to top.

Normalize

Polymodal in interaction with GarageCars.

x = 'GarageArea'
summary(val_train_Xy[x])
   GarageArea    
 Min.   :   0.0  
 1st Qu.: 312.0  
 Median : 480.0  
 Mean   : 469.3  
 3rd Qu.: 576.0  
 Max.   :1418.0  
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 50,
  t_binw = 1
)
NULL

x = 'sqrt(GarageArea)'
val_train_Xy = val_train_Xy %>%
  mutate('sqrt(GarageArea)' = sqrt(GarageArea))

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 sqrt(GarageArea)
 Min.   : 0.00   
 1st Qu.:17.66   
 Median :21.91   
 Mean   :20.68   
 3rd Qu.:24.00   
 Max.   :37.66   
sum_and_trans_cont(
  data = val_train_Xy,
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 1,
  t_binw = 1
)
NULL

Winsorize

x = 'sqrt(GarageArea)'
df = filter(val_train_Xy, GarageArea != 0)

qqnorm(y = df$GarageArea, ylab = 'GarageArea')
qqline(y = df$GarageArea, ylab = 'GarageArea')


qqnorm(y = df[[x]], ylab = x)
qqline(y = df[[x]], ylab = x)


Win_sqrt_x = Winsorize(
  x = df[[x]],
  probs = c(0, 0.998),
  na.rm = T
)

qqnorm(y = Win_sqrt_x, ylab = 'Win_sqrt_x')
qqline(y = Win_sqrt_x, ylab = 'Win_sqrt_x')


Win_raw_x = Winsorize(
  x = df$GarageArea,
  probs = c(0, 0.987),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'Win_raw_x')
qqline(y = Win_raw_x, ylab = 'Win_raw_x')


print(shapiro.test(x = val_train_Xy$GarageArea))

    Shapiro-Wilk normality test

data:  val_train_Xy$GarageArea
W = 0.97888, p-value = 1.218e-08
print(shapiro.test(x = val_train_Xy$`sqrt(GarageArea)`))

    Shapiro-Wilk normality test

data:  val_train_Xy$`sqrt(GarageArea)`
W = 0.85021, p-value < 2.2e-16
print(shapiro.test(x = Win_sqrt_x))

    Shapiro-Wilk normality test

data:  Win_sqrt_x
W = 0.98761, p-value = 1.749e-05
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.97029, p-value = 1.824e-10
min_val = min(Win_sqrt_x)
max_val = max(Win_sqrt_x)
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(sqrt(GarageArea))' = ifelse(
      GarageArea == 0,
      0,
      Winsorize(
        sqrt(GarageArea),
        minval = min_val,
        maxval = max_val
      )
    )
  )

Correlations

x = 'Win(sqrt(GarageArea))'
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('GarageArea', 'sqrt(GarageArea)', x)

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
   GarageArea       sqrt(GarageArea)   Win(sqrt(GarageArea))
 Min.   :0.006474   Min.   :0.001747   Min.   :0.001705     
 1st Qu.:0.139205   1st Qu.:0.121113   1st Qu.:0.121213     
 Median :0.324645   Median :0.268493   Median :0.264631     
 Mean   :0.317963   Mean   :0.287737   Mean   :0.286712     
 3rd Qu.:0.430672   3rd Qu.:0.375451   3rd Qu.:0.372716     
 Max.   :0.873194   Max.   :0.875112   Max.   :0.876250     
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    .data[[x]] != 0
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs (no 0s):")
[1] "Summary of absolute values of Pearson's Rs (no 0s):"
df = abs(df)
summary(abs(df))
   GarageArea       sqrt(GarageArea)   Win(sqrt(GarageArea))
 Min.   :0.002476   Min.   :0.002494   Min.   :0.002863     
 1st Qu.:0.128551   1st Qu.:0.144903   1st Qu.:0.145087     
 Median :0.337145   Median :0.322871   Median :0.319261     
 Mean   :0.315598   Mean   :0.315749   Mean   :0.314570     
 3rd Qu.:0.456501   3rd Qu.:0.470921   3rd Qu.:0.470155     
 Max.   :0.819002   Max.   :0.837390   Max.   :0.840302     
 NA's   :1          NA's   :1          NA's   :1            
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features (no 0s)')


y_lst = c('log(SalePrice)')
for (feat in x_lst) {
  plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst)
}

Transforming and Winsorizing doesn’t help the overall correlation to SalePrice. Let’s facet it out and see.

Garage Area and Cars by Type

df = filter(
  val_train_Xy,
  (GarageType %in% c('Attchd', 'Detchd', 'BuiltIn')) &
    (GarageCars != 4) # Ony one point in this subset with 4 and it doesn't really change the correlations, but it saves viz space.
)

ggplot(df, aes(x = `Win(sqrt(GarageArea))`, y = `log(SalePrice)`)) +
  geom_jitter(alpha = 0.5) +
  geom_smooth(method = 'lm') +
  facet_grid(rows = vars(GarageType), cols = vars(GarageCars.fact))


sum_df0 = df %>%
  summarize(
    n = n(),
    GarageArea_cor = cor(
      x = .data$`Win(log(SalePrice))`,
      y = .data$`GarageArea`
    ),
    sqrtArea_cor = cor(
      x = .data$`Win(log(SalePrice))`,
      y = .data$`sqrt(GarageArea)`
    ),
    WinsqrtArea_cor = cor(
      x = .data$`Win(log(SalePrice))`,
      y = .data$`Win(sqrt(GarageArea))`
    )
  )

min_n = 30

# sum_df1 = df %>%
#   group_by(GarageCars.fact, GarageType) %>%
#   summarize(
#     n = n(),
#     GarageArea_cor = cor(
#       x = .data$`Win(log(SalePrice))`,
#       y = .data$`GarageArea`
#     ),
#     sqrtArea_cor = cor(
#       x = .data$`Win(log(SalePrice))`,
#       y = .data$`sqrt(GarageArea)`
#     ),
#     WinsqrtArea_cor = cor(
#       x = .data$`Win(log(SalePrice))`,
#       y = .data$`Win(sqrt(GarageArea))`
#     )
#   ) %>%
#   filter(n >= min_n)

sum_df2 = df %>% group_by(GarageType) %>%
  summarize(
    n = n(),
    GarageArea_cor = cor(
      x = .data$`Win(log(SalePrice))`,
      y = .data$`GarageArea`
    ),
    sqrtArea_cor = cor(
      x = .data$`Win(log(SalePrice))`,
      y = .data$`sqrt(GarageArea)`
    ),
    WinsqrtArea_cor = cor(
      x = .data$`Win(log(SalePrice))`,
      y = .data$`Win(sqrt(GarageArea))`
    )
  ) %>%
  filter(n >= min_n)

sum_df3 = df %>% group_by(GarageCars.fact) %>%
  summarize(
    n = n(),
    GarageArea_cor = cor(
      x = .data$`Win(log(SalePrice))`,
      y = .data$`GarageArea`
    ),
    sqrtArea_cor = cor(
      x = .data$`Win(log(SalePrice))`,
      y = .data$`sqrt(GarageArea)`
    ),
    WinsqrtArea_cor = cor(
      x = .data$`Win(log(SalePrice))`,
      y = .data$`Win(sqrt(GarageArea))`
    )
  ) %>%
  filter(n >= min_n)

sum_df0 %>%
  merge(y = sum_df2, all = T) %>%
  merge(y = sum_df3, all = T) %>%
  # merge(y = sum_df1, all = T) %>%
  select(
    c('GarageType', 'GarageCars.fact', 'n', 'GarageArea_cor',
      'sqrtArea_cor', 'WinsqrtArea_cor')
  ) %>%
  arrange(GarageType, GarageCars.fact)

Transforming and Winsorizing GarageArea improves correlation to the target variable (“Win(log(SalePrice))”) somewhat when excluding houses without garages. Grouping by garage type helps in some cases.

Whether to transform the variable or not may depend on which ML algorithm we’re using and how. A decision tree will likely be indifferent to tranformations, though it may benefit from noise reduction with Winsorization. A linear regression will only benefit if type and/or missingness is factored in, as would KNN.

Grouping by number of cars lowers the area:price correlation to no correlation. And, in fact, number of cars has a stronger correlation to the target variable than area does. Let’s see how grouping by type further improves that correlation.

df = filter(
  val_train_Xy,
  (GarageType %in% c('Attchd', 'Detchd', 'BuiltIn'))
)

ggplot(df, aes(x = GarageCars, y = `log(SalePrice)`)) +
  geom_jitter(alpha = 0.5) +
  geom_smooth(method = 'lm') +
  facet_wrap(facets = vars(GarageType))


sum_df0 = df %>%
  summarize(
    n = n(),
    Cars_cor = cor(
      x = .data$`Win(log(SalePrice))`,
      y = .data$GarageCars
    ),
    WinCars_cor = cor(
      x = .data$`Win(log(SalePrice))`,
      y = .data$`Win(GarageCars)`
    ),
    WinsqrtArea_cor = cor(
      x = .data$`Win(log(SalePrice))`,
      y = .data$`Win(sqrt(GarageArea))`
    )
  )

sum_df2 = df %>% group_by(GarageType) %>%
  summarize(
    n = n(),
    Cars_cor = cor(
      x = .data$`Win(log(SalePrice))`,
      y = .data$GarageCars
    ),
    WinCars_cor = cor(
      x = .data$`Win(log(SalePrice))`,
      y = .data$`Win(GarageCars)`
    ),
    WinsqrtArea_cor = cor(
      x = .data$`Win(log(SalePrice))`,
      y = .data$`Win(sqrt(GarageArea))`
    )
  )

sum_df0 %>%
  merge(y = sum_df2, all = T) %>%
  select(
    c('GarageType', 'n', 'Cars_cor', 'WinCars_cor', 'WinsqrtArea_cor')
  ) %>%
  arrange(GarageType)

It looks worth dropping GarageArea in favor of GarageCars. There is an argument against linear regression using discrete variables, but it seems to work nonetheless.

Winsorizing GarageCars only produces a marginal benefit which may prove spurious as it only adjusts one point. Plus, it reduces normality. So, I’ll just use the raw feature.

There are likely other features at play, like year built, that also affect things like the difference in the prices of two-car attached garages and two-car detached garages.

fenced_jbv(
  data = df[df$GarageCars != 4, ],
  x = 'GarageType',
  y = 'log(SalePrice)',
  jit_col = 'YearBuilt.fact',
  leg_lbl = 'Year Built'
) +
  facet_wrap(facets = vars(GarageCars.fact))

val_train_Xy = select(
  val_train_Xy,
  -c('GarageCars.fact', 'GarageCars.plus1', 'GarageCars.bin',
     'GarageCars.bin.fact', 'GarageArea', 'sqrt(GarageArea)',
     'Win(sqrt(GarageArea))')
)

GarageQual, Cond

Back to top.

summary(val_train_Xy[ , c('GarageQual', 'GarageCond')])
 GarageQual GarageCond
 None: 40   None: 40  
 Po  :  1   Po  :  4  
 Fa  : 29   Fa  : 20  
 TA  :633   TA  :646  
 Gd  : 11   Gd  :  4  
 Ex  :  1   Ex  :  1  
val_train_Xy = select(val_train_Xy, -c('GarageQual', 'GarageCond'))

PavedDrive

Back to top.

summary(val_train_Xy$PavedDrive)
None    N    P    Y 
   0   53   19  643 
val_train_Xy = select(val_train_Xy, -c('PavedDrive'))

WoodDeckSF

Back to top.

Normalize

x = 'WoodDeckSF'
summary(val_train_Xy[x])
   WoodDeckSF    
 Min.   :  0.00  
 1st Qu.:  0.00  
 Median :  0.00  
 Mean   : 96.78  
 3rd Qu.:175.50  
 Max.   :736.00  
sum_and_trans_cont(
  data = val_train_Xy[val_train_Xy$WoodDeckSF != 0, ],
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 10,
  t_binw = .1
)
NULL

x = 'cbrt(WoodDeckSF)'
val_train_Xy = val_train_Xy %>%
  mutate('cbrt(WoodDeckSF)' = (WoodDeckSF)^(1/3))

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 cbrt(WoodDeckSF)
 Min.   :0.000   
 1st Qu.:0.000   
 Median :0.000   
 Mean   :2.712   
 3rd Qu.:5.599   
 Max.   :9.029   
sum_and_trans_cont(
  data = val_train_Xy[val_train_Xy[[x]] != 0, ],
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = .1,
  t_binw = .1
)
NULL

Winsorize

df = filter(val_train_Xy, WoodDeckSF != 0)

qqnorm(y = df$WoodDeckSF, ylab = 'WoodDeckSF')
qqline(y = df$WoodDeckSF, ylab = 'WoodDeckSF')


qqnorm(y = df[[x]], ylab = x)
qqline(y = df[[x]], ylab = x)


Win_cbrt_x = Winsorize(
  x = df[[x]],
  probs = c(0.01, 0.99),
  na.rm = T
)

qqnorm(y = Win_cbrt_x, ylab = x)
qqline(y = Win_cbrt_x, ylab = x)


Win_raw_x = Winsorize(
  x = df$WoodDeckSF,
  probs = c(0, 0.95),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'WoodDeckSF')
qqline(y = Win_raw_x, ylab = 'WoodDeckSF')


print(shapiro.test(x = df$WoodDeckSF))

    Shapiro-Wilk normality test

data:  df$WoodDeckSF
W = 0.88365, p-value = 1.951e-15
print(shapiro.test(x = df[[x]]))

    Shapiro-Wilk normality test

data:  df[[x]]
W = 0.98712, p-value = 0.003914
print(shapiro.test(x = Win_cbrt_x))

    Shapiro-Wilk normality test

data:  Win_cbrt_x
W = 0.98648, p-value = 0.002765
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.93225, p-value = 2.343e-11
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(cbrt(WoodDeckSF))' = ifelse(
      WoodDeckSF == 0,
      0,
      Winsorize(
        WoodDeckSF^(1/3),
        minval = min(Win_cbrt_x),
        maxval = max(Win_cbrt_x)
      )
    )
  ) %>%
  mutate(
    'Win(WoodDeckSF)' = ifelse(
      WoodDeckSF == 0,
      0,
      Winsorize(
        WoodDeckSF,
        minval = min(Win_raw_x),
        maxval = max(Win_raw_x)
      )
    )
  )

Binarize

val_train_Xy = val_train_Xy %>%
  mutate('WoodDeck.bin' = ifelse(WoodDeckSF == 0, 0, 1)) %>%
  mutate('WoodDeck.bin.fact' = factor(WoodDeck.bin, ordered = T))

x = 'WoodDeck.bin.fact'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "0" "1"

Correlations

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('WoodDeckSF', 'cbrt(WoodDeckSF)', 'Win(cbrt(WoodDeckSF))',
          'Win(WoodDeckSF)', 'WoodDeck.bin')
x = 'Win(cbrt(WoodDeckSF))'

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
   WoodDeckSF       cbrt(WoodDeckSF)   Win(cbrt(WoodDeckSF))
 Min.   :0.006145   Min.   :0.004212   Min.   :0.004404     
 1st Qu.:0.084695   1st Qu.:0.069548   1st Qu.:0.069704     
 Median :0.176322   Median :0.161833   Median :0.161822     
 Mean   :0.160453   Mean   :0.165207   Mean   :0.165111     
 3rd Qu.:0.234533   3rd Qu.:0.252250   3rd Qu.:0.251680     
 Max.   :0.328898   Max.   :0.355420   Max.   :0.355488     
 Win(WoodDeckSF)     WoodDeck.bin     
 Min.   :0.001863   Min.   :0.002522  
 1st Qu.:0.082133   1st Qu.:0.055760  
 Median :0.179367   Median :0.161167  
 Mean   :0.163279   Mean   :0.149999  
 3rd Qu.:0.242096   3rd Qu.:0.228064  
 Max.   :0.339011   Max.   :0.330345  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    .data[[x]] != 0
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs (no 0s):")
[1] "Summary of absolute values of Pearson's Rs (no 0s):"
df = abs(df)
summary(abs(df))
   WoodDeckSF       cbrt(WoodDeckSF)   Win(cbrt(WoodDeckSF))
 Min.   :0.001777   Min.   :0.002974   Min.   :0.004668     
 1st Qu.:0.060621   1st Qu.:0.056354   1st Qu.:0.056724     
 Median :0.112829   Median :0.144985   Median :0.142206     
 Mean   :0.114102   Mean   :0.123596   Mean   :0.124123     
 3rd Qu.:0.157044   3rd Qu.:0.172657   3rd Qu.:0.169070     
 Max.   :0.285351   Max.   :0.298849   Max.   :0.302455     
                                                            
 Win(WoodDeckSF)     WoodDeck.bin
 Min.   :0.001231   Min.   : NA  
 1st Qu.:0.060845   1st Qu.: NA  
 Median :0.122578   Median : NA  
 Mean   :0.116930   Mean   :NaN  
 3rd Qu.:0.155611   3rd Qu.: NA  
 Max.   :0.304426   Max.   : NA  
                    NA's   :55   
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features (no 0s)')


y_lst = c('Win(log(SalePrice))')
for (feat in x_lst) {
  plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst)
  # plot_scat_pairs(
  #   df = filter(val_train_Xy, WoodDeckSF != 0),
  #   x = feat,
  #   y_lst = y_lst
  # )
}

There’s no real correlation between deck square footage and the target price when you remove houses without decks; the binary version does most of the work. But, the transformation does it a little better and does offer more normalized distance between points for KNN.

Hard Code

x = 'Win(cbrt(WoodDeckSF))'

min_val = min(Win_cbrt_x)
max_val = max(Win_cbrt_x)
print(paste("min_val:", min_val))
[1] "min_val: 2.99287415882937"
print(paste("max_val:", max_val))
[1] "max_val: 8.48252791006478"
# Already hard coded above

val_train_Xy = select(
  val_train_Xy,
  -c('WoodDeck.bin', 'WoodDeck.bin.fact', 'Win(WoodDeckSF)')
)

ggplot(val_train_Xy, aes(x = .data[[x]])) +
  geom_histogram(binwidth = .25)

OpenPorchSF

Back to top.

Normalize

x = 'OpenPorchSF'
summary(val_train_Xy[x])
  OpenPorchSF    
 Min.   :  0.00  
 1st Qu.:  0.00  
 Median : 27.00  
 Mean   : 47.46  
 3rd Qu.: 72.00  
 Max.   :547.00  
sum_and_trans_cont(
  data = val_train_Xy[val_train_Xy$OpenPorchSF != 0, ],
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 10,
  t_binw = 0.1
)
NULL

x = 'cbrt(OpenPorchSF)'
val_train_Xy = val_train_Xy %>%
  mutate('cbrt(OpenPorchSF)' = OpenPorchSF^(1/3))

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 cbrt(OpenPorchSF)
 Min.   :0.000    
 1st Qu.:0.000    
 Median :3.000    
 Mean   :2.276    
 3rd Qu.:4.160    
 Max.   :8.178    
sum_and_trans_cont(
  data = val_train_Xy[val_train_Xy[[x]] != 0, ],
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 0.1,
  t_binw = 0.1
)
NULL

Winsorize

df = filter(val_train_Xy, OpenPorchSF != 0)
x = 'cbrt(OpenPorchSF)'

qqnorm(y = df$OpenPorchSF, ylab = 'OpenPorchSF')
qqline(y = df$OpenPorchSF, ylab = 'OpenPorchSF')


qqnorm(y = df[[x]], ylab = x)
qqline(y = df[[x]], ylab = x)


Win_cbrt_x = Winsorize(
  x = df[[x]],
  probs = c(0.001, 0.994),
  na.rm = T
)

qqnorm(y = Win_cbrt_x, ylab = x)
qqline(y = Win_cbrt_x, ylab = x)


Win_raw_x = Winsorize(
  x = df$OpenPorchSF,
  probs = c(0, 0.95),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'OpenPorchSF')
qqline(y = Win_raw_x, ylab = 'OpenPorchSF')


print(shapiro.test(x = df$OpenPorchSF))

    Shapiro-Wilk normality test

data:  df$OpenPorchSF
W = 0.79866, p-value < 2.2e-16
print(shapiro.test(x = df[[x]]))

    Shapiro-Wilk normality test

data:  df[[x]]
W = 0.97116, p-value = 5.932e-07
print(shapiro.test(x = Win_cbrt_x))

    Shapiro-Wilk normality test

data:  Win_cbrt_x
W = 0.97391, p-value = 1.921e-06
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.89182, p-value = 6.251e-16
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(cbrt(OpenPorchSF))' = ifelse(
      OpenPorchSF == 0,
      0,
      Winsorize(
        OpenPorchSF^(1/3),
        # probs = c(0.001, 0.994),
        # na.rm = T
        minval = min(Win_cbrt_x),
        maxval = max(Win_cbrt_x)
      )
    )
  )

Looks like some polymodality happening. Year built doesn’t seem to explain it. I’m not going to manually search for it any further, but a decision tree or other ML algorithm may find and “factor” in the hidden interaction.

ggplot(
  filter(val_train_Xy, OpenPorchSF != 0),
  aes(x = `cbrt(OpenPorchSF)`)
) +
  geom_histogram(binwidth = 0.1) +
  facet_wrap(facets = vars(YearBuilt.fact), ncol = 1)


ggplot(
  filter(val_train_Xy, OpenPorchSF != 0),
  aes(x = `cbrt(OpenPorchSF)`, y = `log(SalePrice)`)
) +
  geom_point(aes(color = YearBuilt))

Binarize

val_train_Xy = val_train_Xy %>%
  mutate('OpenPorch.bin' = ifelse(OpenPorchSF == 0, 0, 1)) %>%
  mutate('OpenPorch.bin.fact' = factor(OpenPorch.bin, ordered = T))

x = 'OpenPorch.bin.fact'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "1" "0"

Correlations

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('OpenPorchSF', 'cbrt(OpenPorchSF)', 'Win(cbrt(OpenPorchSF))',
          'OpenPorch.bin')
x = 'Win(cbrt(OpenPorchSF))'

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
  OpenPorchSF       cbrt(OpenPorchSF)  Win(cbrt(OpenPorchSF))
 Min.   :0.004127   Min.   :0.003166   Min.   :0.002332      
 1st Qu.:0.059251   1st Qu.:0.090986   1st Qu.:0.091328      
 Median :0.142723   Median :0.167835   Median :0.168485      
 Mean   :0.151759   Mean   :0.202395   Mean   :0.202442      
 3rd Qu.:0.232678   3rd Qu.:0.325809   3rd Qu.:0.326224      
 Max.   :0.339325   Max.   :0.460438   Max.   :0.459971      
 OpenPorch.bin     
 Min.   :0.000127  
 1st Qu.:0.082138  
 Median :0.152291  
 Mean   :0.203538  
 3rd Qu.:0.356361  
 Max.   :0.465546  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    .data[[x]] != 0
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs (no 0s):")
[1] "Summary of absolute values of Pearson's Rs (no 0s):"
df = abs(df)
summary(abs(df))
  OpenPorchSF       cbrt(OpenPorchSF)   Win(cbrt(OpenPorchSF))
 Min.   :0.002912   Min.   :0.0005588   Min.   :0.001252      
 1st Qu.:0.037026   1st Qu.:0.0371173   1st Qu.:0.033447      
 Median :0.070241   Median :0.0705708   Median :0.067662      
 Mean   :0.074408   Mean   :0.0750998   Mean   :0.073149      
 3rd Qu.:0.106605   3rd Qu.:0.1177230   3rd Qu.:0.112676      
 Max.   :0.192231   Max.   :0.2019503   Max.   :0.191297      
                                                              
 OpenPorch.bin
 Min.   : NA  
 1st Qu.: NA  
 Median : NA  
 Mean   :NaN  
 3rd Qu.: NA  
 Max.   : NA  
 NA's   :57   
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features (no 0s)')


y_lst = c('Win(log(SalePrice))')
for (feat in x_lst) {
  plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst)
  plot_scat_pairs(
    df = val_train_Xy[val_train_Xy[[x]] != 0, ],
    x = feat,
    y_lst = y_lst
  )
}

In this case, the binary variable is doing all the work.

val_train_Xy = select(
  val_train_Xy,
  -c('cbrt(OpenPorchSF)', 'Win(cbrt(OpenPorchSF))', 'OpenPorch.bin.fact')
)

EnclosedPorch

Back to top.

Normalize

x = 'EnclosedPorch'
summary(val_train_Xy[x])
 EnclosedPorch   
 Min.   :  0.00  
 1st Qu.:  0.00  
 Median :  0.00  
 Mean   : 19.09  
 3rd Qu.:  0.00  
 Max.   :294.00  
sum_and_trans_cont(
  data = val_train_Xy[val_train_Xy$EnclosedPorch != 0, ],
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 5,
  t_binw = 5
)
NULL

Winsorize

df = filter(val_train_Xy, EnclosedPorch != 0)

qqnorm(y = df[[x]], ylab = x)
qqline(y = df[[x]], ylab = x)


Win_raw_x = Winsorize(
  x = df$EnclosedPorch,
  probs = c(0.001, 0.999),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'Win_raw_x')
qqline(y = Win_raw_x, ylab = 'Win_raw_x')


print(shapiro.test(x = df[[x]]))

    Shapiro-Wilk normality test

data:  df[[x]]
W = 0.98306, p-value = 0.2587
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.9827, p-value = 0.2437

Binarize

val_train_Xy = val_train_Xy %>%
  mutate('EnclosedPorch.bin' = ifelse(EnclosedPorch == 0, 0, 1)) %>%
  mutate(
    'EnclosedPorch.bin.fact' = factor(
      EnclosedPorch.bin,
      ordered = T
    )
  )

x = 'EnclosedPorch.bin.fact'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "0" "1"

Correlations

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('EnclosedPorch', 'EnclosedPorch.bin')
x = 'EnclosedPorch'

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
 EnclosedPorch      EnclosedPorch.bin 
 Min.   :0.004006   Min.   :0.001413  
 1st Qu.:0.025521   1st Qu.:0.043374  
 Median :0.069574   Median :0.095328  
 Mean   :0.087443   Mean   :0.113655  
 3rd Qu.:0.138839   3rd Qu.:0.165249  
 Max.   :0.371542   Max.   :0.458928  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    .data[[x]] != 0
  ),
  x_lst = c(x),
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs (no 0s):")
[1] "Summary of absolute values of Pearson's Rs (no 0s):"
df = abs(df)
summary(abs(df))
 EnclosedPorch     
 Min.   :0.005257  
 1st Qu.:0.089806  
 Median :0.169637  
 Mean   :0.184809  
 3rd Qu.:0.248067  
 Max.   :0.474650  
 NA's   :3         
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features (no 0s)')


y_lst = c('log(SalePrice)')
for (feat in x_lst) {
  plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst)
  plot_scat_pairs(
    df = val_train_Xy[val_train_Xy[[x]] != 0, ],
    x = feat,
    y_lst = y_lst
  )
}

Houses with enclosed porches are significantly cheaper than those without, maybe due to a confounding variable like year built. There is a weak, if existent, positive correlation between square footage and price within those that have them.

fenced_jbv(
  data = val_train_Xy,
  x = 'EnclosedPorch.bin.fact',
  y = 'log(SalePrice)',
  jit_col = 'YearBuilt',
  jit_alpha = 1
) +
  facet_wrap(facets = vars(YearBuilt.fact))


val_train_Xy$resids = lm(
  `log(SalePrice)` ~ `sqrt(Age)`,
  val_train_Xy
)$residuals

ggplot(val_train_Xy, aes(x = EnclosedPorch, y = resids)) +
  geom_point() +
  geom_smooth(method = 'lm')

val_train_Xy = select(
  val_train_Xy, -c('EnclosedPorch.bin', 'EnclosedPorch.bin.fact')
)

X3SsnPorch

Back to top.

summary(val_train_Xy$X3SsnPorch)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   0.000   0.000   4.505   0.000 508.000 
val_train_Xy = select(val_train_Xy, -c('X3SsnPorch'))

ScreenPorch

Back to top.

Normalize

x = 'ScreenPorch'
summary(val_train_Xy[x])
  ScreenPorch    
 Min.   :  0.00  
 1st Qu.:  0.00  
 Median :  0.00  
 Mean   : 16.48  
 3rd Qu.:  0.00  
 Max.   :480.00  
sum_and_trans_cont(
  data = val_train_Xy[val_train_Xy$ScreenPorch != 0, ],
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 10,
  t_binw = 0.1
)
NULL

x = 'cbrt(ScreenPorch)'
val_train_Xy = val_train_Xy %>%
  mutate('cbrt(ScreenPorch)' = ScreenPorch^(1/3))

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 cbrt(ScreenPorch)
 Min.   :0.0000   
 1st Qu.:0.0000   
 Median :0.0000   
 Mean   :0.4832   
 3rd Qu.:0.0000   
 Max.   :7.8297   
sum_and_trans_cont(
  data = val_train_Xy[val_train_Xy$`cbrt(ScreenPorch)` != 0, ],
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 0.1,
  t_binw = 0.1
)
NULL

Winsorize

x = 'cbrt(ScreenPorch)'
df = filter(val_train_Xy, ScreenPorch != 0) 

qqnorm(y = df$ScreenPorch, ylab = 'ScreenPorch')
qqline(y = df$ScreenPorch)


qqnorm(y = df[[x]], ylab = x)
qqline(y = df[[x]])


Win_cbrt_x = Winsorize(
  df[[x]],
  probs = c(0.05, 0.95),
  na.rm = T
)

qqnorm(y = Win_cbrt_x, ylab = 'Win_cbrt_x')
qqline(y = Win_cbrt_x)


Win_raw_x = Winsorize(
  df$ScreenPorch,
  probs = c(0.05, 0.95),
  na.rm = T
)

qqnorm(y = Win_raw_x, ylab = 'Win_raw_x')
qqline(y = Win_raw_x)


print(shapiro.test(x = df$ScreenPorch))

    Shapiro-Wilk normality test

data:  df$ScreenPorch
W = 0.92914, p-value = 0.001652
print(shapiro.test(x = df[[x]]))

    Shapiro-Wilk normality test

data:  df[[x]]
W = 0.97514, p-value = 0.2487
print(shapiro.test(x = Win_cbrt_x))

    Shapiro-Wilk normality test

data:  Win_cbrt_x
W = 0.96719, p-value = 0.1008
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.92848, p-value = 0.001547
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(cbrt(ScreenPorch))' = ifelse(
      ScreenPorch == 0,
      0,
      Winsorize(
        ScreenPorch^(1/3),
        minval = min(Win_cbrt_x),
        maxval = max(Win_cbrt_x)
      )
    )
  )

Binarize

val_train_Xy = val_train_Xy %>%
  mutate('ScreenPorch.bin' = ifelse(ScreenPorch ==0, 0, 1)) %>%
  mutate('ScreenPorch.bin.fact' = factor(ScreenPorch.bin, ordered = T))

x = 'ScreenPorch.bin.fact'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "0" "1"

Correlations

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('ScreenPorch', 'cbrt(ScreenPorch)', 'Win(cbrt(ScreenPorch))',
          'ScreenPorch.bin')
x = 'Win(cbrt(ScreenPorch))'

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
  ScreenPorch       cbrt(ScreenPorch)  Win(cbrt(ScreenPorch))
 Min.   :0.000127   Min.   :0.001702   Min.   :0.0007076     
 1st Qu.:0.033205   1st Qu.:0.032575   1st Qu.:0.0310287     
 Median :0.052721   Median :0.054455   Median :0.0552326     
 Mean   :0.062430   Mean   :0.060759   Mean   :0.0601016     
 3rd Qu.:0.074751   3rd Qu.:0.083482   3rd Qu.:0.0840852     
 Max.   :0.225009   Max.   :0.226688   Max.   :0.2256480     
 ScreenPorch.bin   
 Min.   :0.001074  
 1st Qu.:0.026853  
 Median :0.056082  
 Mean   :0.057789  
 3rd Qu.:0.086152  
 Max.   :0.220821  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    .data[[x]] != 0
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs (no 0s):")
[1] "Summary of absolute values of Pearson's Rs (no 0s):"
df = abs(df)
summary(abs(df))
  ScreenPorch      cbrt(ScreenPorch)  Win(cbrt(ScreenPorch))
 Min.   :0.01673   Min.   :0.003906   Min.   :0.0138        
 1st Qu.:0.09574   1st Qu.:0.106601   1st Qu.:0.1020        
 Median :0.14405   Median :0.143076   Median :0.1361        
 Mean   :0.14621   Mean   :0.159218   Mean   :0.1533        
 3rd Qu.:0.20818   3rd Qu.:0.233239   3rd Qu.:0.2118        
 Max.   :0.37341   Max.   :0.325564   Max.   :0.2796        
 NA's   :1         NA's   :1          NA's   :1             
 ScreenPorch.bin
 Min.   : NA    
 1st Qu.: NA    
 Median : NA    
 Mean   :NaN    
 3rd Qu.: NA    
 Max.   : NA    
 NA's   :57     
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features (no 0s)')


y_lst = c('Win(log(SalePrice))')
for (feat in x_lst) {
  plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst)
  plot_scat_pairs(
    df = val_train_Xy[val_train_Xy[[x]] != 0, ],
    x = feat,
    y_lst = y_lst
  )
}

The binary doesn’t seem to make a significant difference as other binaries like WoodDeck and OpenPorch. So, it’s not worth using alone, but might be useful in interaction with the area in a linear regression. Decision trees should be able to do without the binary feature. The transformation may help KNN, but like a decision tree, I would want to avoid overweighting by including both.

“Controlling”

fenced_jbv(
  data = val_train_Xy,
  x = 'ScreenPorch.bin.fact',
  y = 'log(SalePrice)',
  jit_col = 'YearBuilt',
  jit_alpha = 1
) +
  facet_wrap(facets = vars(YearBuilt.fact))


val_train_Xy$resids = lm(
  `log(SalePrice)` ~ `sqrt(Age)`,
  val_train_Xy
)$residuals

ggplot(val_train_Xy, aes(x = `cbrt(ScreenPorch)`, y = resids)) +
  geom_point() +
  geom_smooth(method = 'lm')


print("Correlation of cbrt(ScreenPorch) to residuals of `log(SalePrice)` ~ `sqrt(Age)`")
[1] "Correlation of cbrt(ScreenPorch) to residuals of `log(SalePrice)` ~ `sqrt(Age)`"
print(cor(x = val_train_Xy$`cbrt(ScreenPorch)`, y = val_train_Xy$resids))
[1] 0.2266884
df = filter(val_train_Xy, ScreenPorch != 0)

ggplot(df, aes(x = `cbrt(ScreenPorch)`, y = resids)) +
  geom_point() +
  geom_smooth(method = 'lm')


print("Exluding ScreenPorch 0s:")
[1] "Exluding ScreenPorch 0s:"
print(cor(x = val_train_Xy$`cbrt(ScreenPorch)`, y = val_train_Xy$resids))
[1] 0.2266884

This feature doesn’t seem to have much to offer. But, I’ll leave it anyway and let feature selection during modeling suss that out.

val_train_Xy = select(
  val_train_Xy, -c('ScreenPorch.bin.fact', 'Win(cbrt(ScreenPorch))')
)

PoolArea, QC

Back to top.

summary(val_train_Xy$PoolQC)
None   Po   Fa   TA   Gd   Ex 
 712    0    1    0    1    1 
val_train_Xy = select(val_train_Xy, -c('PoolArea', 'PoolQC'))

Fence

Back to top.

x = 'Fence'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "None"  "MnPrv"

Binarize

val_train_Xy = val_train_Xy %>%
  mutate('Fence.bin' = ifelse(Fence == 'None', 0, 1)) %>%
  mutate('Fence.bin.fact' = factor(Fence.bin, ordered = T))

x = 'Fence.bin.fact'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "0" "1"

Having a Fence seems to detract value, probably due to an interaction with another variable as we saw with EnclosedPorch and age. Unlike EnclosedPorch, it’s not immediately obvious what the other variable is, and “controlling” for it with a linear regression may actually increase Fence’s significance. So, rather than hunting for the other variable(s) or dropping this one, I’ll leave it and see if ML modeling can make use of it.

val_train_Xy = select(
  val_train_Xy,
  -c('Fence.bin', 'Fence.bin.fact')
)

MiscFeature, MiscVal

Back to top.

MiscVal is kind of a cheater variable. It should have precisely 1 for its coefficient. Otherwise, I would just drop it for so few observations; it might just throw of the regression. If I keep it, it should be transformed in the same way that the target variable is.

It also looks like the presence of a miscellaneous improvement is associated with a lower price if anything.

x = 'MiscFeature'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
character(0)

MiscVal

Back to top.

Normalize

x = 'MiscVal'
summary(val_train_Xy[x])
    MiscVal        
 Min.   :    0.00  
 1st Qu.:    0.00  
 Median :    0.00  
 Mean   :   54.19  
 3rd Qu.:    0.00  
 Max.   :15500.00  
sum_and_trans_cont(
  data = val_train_Xy[val_train_Xy$MiscVal != 0, ],
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = 200,
  t_binw = .1
)
NULL

x = 'log2(MiscVal)'
val_train_Xy = val_train_Xy %>%
  mutate(
    'log2(MiscVal)' = ifelse(
      MiscVal == 0,
      0,
      log2(MiscVal)
    )
  )

# Recalculate best normalizers.
num_feats = colnames(select(val_train_Xy, where(is.numeric)))
best_normalizers = find_best_normalizer_per_feat(
  df = val_train_Xy,
  feats_lst = num_feats,
  funcs_lst = funcs_lst,
  exclude_vals = list(0)
)

summary(val_train_Xy[x])
 log2(MiscVal)    
 Min.   : 0.0000  
 1st Qu.: 0.0000  
 Median : 0.0000  
 Mean   : 0.2708  
 3rd Qu.: 0.0000  
 Max.   :13.9200  
sum_and_trans_cont(
  data = val_train_Xy[val_train_Xy$`log2(MiscVal)` != 0, ],
  x = x,
  func = best_normalizers[[x]]$best_func$func,
  func_name = best_normalizers[[x]]$best_func$name,
  x_binw = .1,
  t_binw = .1
)
NULL

Alright, I’ll give it a natural log like SalePrice, since it is a straight dollar value.

x = 'log(MiscVal)'
val_train_Xy = val_train_Xy %>%
  mutate(
    'log(MiscVal)' = ifelse(
      MiscVal == 0,
      0,
      log(MiscVal)
    )
  )

df = filter(val_train_Xy, MiscVal != 0)

gg = ggplot(df, aes(x = `log(MiscVal)`))
p1 = gg + geom_histogram(binwidth = 0.1)
p2 = gg + geom_boxplot(notch = T)
grid.arrange(p1, p2)

Winsorize

Since this variable is an actual dollar value, Winsorizing it doesn’t really make sense. I’ll check it out anyway.

qqnorm(y = df$MiscVal, ylab = 'MiscVal')
qqline(y = df$MiscVal)


qqnorm(y = df$`log(MiscVal)`, ylab = 'log(MiscVal)')
qqline(y = df$`log(MiscVal)`)


Win_log_x = Winsorize(
  df$`log(MiscVal)`,
  probs = c(0.007, 0.95),
  na.rm = T
)

qqnorm(y = Win_log_x, ylab = 'Win_log_x')
qqline(y = Win_log_x)


Win_raw_x = Winsorize(
  df$MiscVal,
  probs = c(0, 0.95)
)

qqnorm(y = Win_raw_x, ylab = 'Win_raw_x')
qqline(y = Win_raw_x)


print(shapiro.test(x = df$MiscVal))

    Shapiro-Wilk normality test

data:  df$MiscVal
W = 0.49238, p-value = 2.714e-07
print(shapiro.test(x = df$`log(MiscVal)`))

    Shapiro-Wilk normality test

data:  df$`log(MiscVal)`
W = 0.88923, p-value = 0.02603
print(shapiro.test(x = Win_log_x))

    Shapiro-Wilk normality test

data:  Win_log_x
W = 0.89412, p-value = 0.03203
print(shapiro.test(x = Win_raw_x))

    Shapiro-Wilk normality test

data:  Win_raw_x
W = 0.5586, p-value = 1.129e-06
val_train_Xy = val_train_Xy %>%
  mutate(
    'Win(log(MiscVal))' = ifelse(
      MiscVal == 0,
      0,
      Winsorize(
        log(MiscVal),
        minval = min(Win_log_x),
        maxval = max(Win_log_x)
      )
    )
  )

Binarize

val_train_Xy = val_train_Xy %>%
  mutate('MiscVal.bin' = ifelse(MiscVal == 0, 0, 1)) %>%
  mutate('MiscVal.bin.fact' = factor(MiscVal.bin, ordered = T))

x = 'MiscVal.bin.fact'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
character(0)

Correlations

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('MiscVal', 'log2(MiscVal)', 'log(MiscVal)',
          'Win(log(MiscVal))', 'MiscVal.bin')

x = 'log(MiscVal)'

df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    !is.na(.data[[x]])
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs:")
[1] "Summary of absolute values of Pearson's Rs:"
df = abs(df)
summary(abs(df))
    MiscVal         log2(MiscVal)       log(MiscVal)     
 Min.   :0.001150   Min.   :0.001803   Min.   :0.001803  
 1st Qu.:0.006437   1st Qu.:0.013649   1st Qu.:0.013649  
 Median :0.013835   Median :0.028586   Median :0.028586  
 Mean   :0.018486   Mean   :0.032804   Mean   :0.032804  
 3rd Qu.:0.025099   3rd Qu.:0.047086   3rd Qu.:0.047086  
 Max.   :0.083097   Max.   :0.087143   Max.   :0.087143  
 Win(log(MiscVal))   MiscVal.bin     
 Min.   :0.002117   Min.   :0.00218  
 1st Qu.:0.013673   1st Qu.:0.01782  
 Median :0.028980   Median :0.03383  
 Mean   :0.032855   Mean   :0.03774  
 3rd Qu.:0.046462   3rd Qu.:0.05123  
 Max.   :0.086933   Max.   :0.09574  
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features')


df = get_cors(
  data = filter(
    select(val_train_Xy, all_of(num_feats)),
    .data[[x]] != 0
  ),
  x_lst = x_lst,
  feats = num_feats
)
df
print("Summary of absolute values of Pearson's Rs (no 0s):")
[1] "Summary of absolute values of Pearson's Rs (no 0s):"
df = abs(df)
summary(abs(df))
    MiscVal         log2(MiscVal)       log(MiscVal)     
 Min.   :0.006626   Min.   :0.009576   Min.   :0.009576  
 1st Qu.:0.050062   1st Qu.:0.121361   1st Qu.:0.121361  
 Median :0.123861   Median :0.233112   Median :0.233112  
 Mean   :0.139666   Mean   :0.254203   Mean   :0.254203  
 3rd Qu.:0.190636   3rd Qu.:0.369457   3rd Qu.:0.369457  
 Max.   :0.458312   Max.   :0.590246   Max.   :0.590246  
                                                         
 Win(log(MiscVal))   MiscVal.bin 
 Min.   :0.001031   Min.   : NA  
 1st Qu.:0.114223   1st Qu.: NA  
 Median :0.264792   Median : NA  
 Mean   :0.270993   Mean   :NaN  
 3rd Qu.:0.415090   3rd Qu.: NA  
 Max.   :0.610388   Max.   : NA  
                    NA's   :58   
df = melt(df)
ggplot(df, aes(x = variable, y = value)) +
  geom_boxplot(notch = T) +
  ylab(label = 'Absolute Value of Correlation to Other Features (no 0s)')


y_lst = c('Win(log(SalePrice))')
for (feat in x_lst) {
  plot_scat_pairs(df = val_train_Xy, x = feat, y_lst = y_lst)
  plot_scat_pairs(
    df = val_train_Xy[val_train_Xy[[x]] != 0, ],
    x = feat,
    y_lst = y_lst
  )
}

“Controlling”

MiscVal appears to be correlated with the size of the lot and house. Perhaps that will do the heavy lifting and make MiscVal obsolete.

val_train_Xy$resids = lm(
  `log(SalePrice)` ~ `Win(LotArea)`,
  val_train_Xy
)$residuals

print("Correlation of log(MiscVal) to residuals of `log(SalePrice)` ~ `Win(LotArea)`")
[1] "Correlation of log(MiscVal) to residuals of `log(SalePrice)` ~ `Win(LotArea)`"
print(cor(x = val_train_Xy$`log(MiscVal)`, y = val_train_Xy$resids))
[1] -0.08294709
ggplot(val_train_Xy, aes(x = `log(MiscVal)`, y = resids)) +
  geom_point() +
  geom_smooth(method = 'lm') +
  ylab(label = "`log(SalePrice)` ~ `Win(LotArea)`")


df = filter(val_train_Xy, MiscVal != 0)

print("Exluding MiscVal 0s:")
[1] "Exluding MiscVal 0s:"
print(cor(x = df$`log(MiscVal)`, y = df$resids))
[1] 0.4032275
ggplot(df, aes(x = `log(MiscVal)`, y = resids)) +
  geom_point() +
  geom_smooth(method = 'lm') +
  ylab(label = "`log(SalePrice)` ~ `Win(LotArea)`")



val_train_Xy$resids = lm(
  `log(SalePrice)` ~ `log10(log10(LotArea))`,
  val_train_Xy
)$residuals

print("Correlation of log(MiscVal) to residuals of `log(SalePrice)` ~ `log10(log10(LotArea))`")
[1] "Correlation of log(MiscVal) to residuals of `log(SalePrice)` ~ `log10(log10(LotArea))`"
print(cor(x = val_train_Xy$`log(MiscVal)`, y = val_train_Xy$resids))
[1] -0.0788646
ggplot(val_train_Xy, aes(x = `log(MiscVal)`, y = resids)) +
  geom_point() +
  geom_smooth(method = 'lm') +
  ylab(label = "`log(SalePrice)` ~ `log10(log10(LotArea))`")


df = filter(val_train_Xy, MiscVal != 0)

print("Exluding MiscVal 0s:")
[1] "Exluding MiscVal 0s:"
print(cor(x = df$`log(MiscVal)`, y = df$resids))
[1] 0.4945909
ggplot(df, aes(x = `log(MiscVal)`, y = resids)) +
  geom_point() +
  geom_smooth(method = 'lm') +
  ylab(label = "`log(SalePrice)` ~ `log10(log10(LotArea))`")

After factoring in Lot Area, there’s still some correlation between MiscVal and the target once 0s are removed. It may still be worth keeping. We’ll let modeling suss that out.

val_train_Xy = select(
  val_train_Xy, -c('MiscVal.bin', 'MiscVal.bin.fact', 'log2(MiscVal)')
  )

MoSold, YrSold

Back to top.

MoSold somewhat normally distributed around June/July. Conventional wisdom says that summer sales are higher in volume and price, but the data don’t bear that out for price.

Records go from January 2006 through July 2010, so August-December are underrepresented by roughly 20%.

YrSold pretty uniform (about 140-160) except for 2010 which ended in July in this set.

Interesting spike in sales in Spring 2010. Foreclosures coming onto market?

Set to Factors: MoSold, YrSold

val_train_Xy = val_train_Xy %>%
  mutate('MoSold.fact' = factor(MoSold)) %>%
  mutate('YrSold.fact' = factor(YrSold, ordered = T))

x = 'MoSold.fact'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "1"  "7"  "5"  "9"  "12"
x = 'YrSold.fact'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
character(0)

SoldDate

Back to top.

val_train_Xy = val_train_Xy %>%
  mutate(
    SoldDate = as.Date(
      paste(
        as.character(YrSold),
        as.character(MoSold),
        '15',
        sep = '/'
      ),
      format = '%Y/%m/%d'
    )
  )

ggplot(val_train_Xy, aes(x = SoldDate)) +
  geom_bar()


x = 'SoldDate'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

num_feats = colnames(select(val_train_Xy, where(is.numeric)))
x_lst = c('SoldDate')
# 
# df = get_cors(
#   data = filter(
#     select(val_train_Xy, all_of(num_feats)),
#     !is.na(.data[[x]])
#   ),
#   x_lst = x_lst,
#   feats = num_feats
# )
# df
# print("Summary of absolute values of Pearson's Rs:")
# df = abs(df)
# summary(abs(df))
# 
# df = melt(df)
# ggplot(df, aes(x = variable, y = value)) +
#   geom_boxplot(notch = T) +
#   ylab(label = 'Absolute Value of Correlation to Other Features')

y_lst = c('log(SalePrice)')
plot_scat_pairs(df = val_train_Xy, x = x, y_lst = y_lst)
NULL

ggplot(
  val_train_Xy,
  aes(x = SoldDate, y = `log(SalePrice)`)
) +
  geom_point() +
  geom_smooth() +
  geom_smooth(method = 'lm', color = 'Yellow') +
  facet_grid(rows = vars(SaleCondition), cols = vars(SaleType))

None of date seems to matter in this set. Drop it, but no yet.

SaleType

Back to top.

x = 'SaleType'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "WD"  "New"

df = val_train_Xy[val_train_Xy$SaleType %in% c('WD', 'New', 'COD'), ]
gg = ggplot(df, aes(x = SaleType))

gg + geom_bar() +
  facet_grid(rows = vars(MoSold), cols = vars(YrSold)) +
  theme(axis.text.x = element_text(angle = 90, hjust = 1))


gg + geom_bar(position = 'dodge', aes(fill = factor(YrSold)))


ggplot(df, aes(color = SaleType, x = SoldDate)) +
  geom_freqpoly()


# ggplot(df, aes(x = SaleType, y = SalePrice)) +
#   geom_col(position = 'dodge', aes(fill = factor(YrSold), stat = 'mean'))

ggplot(df, aes(x = SaleType, y = `log(SalePrice)`)) +
  geom_boxplot(position = 'dodge', notch = T, aes(color = factor(YrSold)))

You can see new home sales drop off with the crash. Court officer deeds also ticked up, possibly due to more foreclosures. But, there didn’t seem to be a significant difference in sale prices year over year within sale type groups, except between new sales in 2007and 2010 which is not fully represented in this set.

SaleCondition

Back to top.

592 normal, 61 partial (home not completed when last assessed), 43 abnormal (trade, foreclosure, short sale), 11 family. I’m guessing a family sale will be lower in price typically, as will foreclosures and shortsales. I’m not sure what to make of partials; the house wasn’t fully built when assessed, so the price may be askew?

Surprisingly, abnormal sales didn’t seem to vary with the crash. Ames appears to have fared well.

x = 'SaleCondition'
y = 'SalePrice'
summarize_by(data = val_train_Xy, x = x, y = y)
y = 'log(SalePrice)'
sum_and_trans_fact(data = val_train_Xy, x = x, y = y)
NULL

p_vals = get_signif_levels(data = val_train_Xy, x = x, z = y, min_n = 30)

heatmap.2(
    x = as.matrix(p_vals$pval_df),
    scale = 'none',
    Rowv = F,
    Colv = F,
    dendrogram = 'none',
    cellnote = format(p_vals$pval_df, digits = 2),
    notecex = 0.75,
    notecol = 'black',
    main = paste(y, 'p-values'),
    key = F
  )


print(
    paste(
      "Levels w/ significantly different",
      y,
      "than another level:"
    )
  )
[1] "Levels w/ significantly different log(SalePrice) than another level:"
print(p_vals$signif_levels)
[1] "Normal"  "Abnorml" "Partial"
df = filter(val_train_Xy, SaleCondition %in% c('Normal', 'Abnorml', 'Partial'))

gg = ggplot(df, aes(x = SaleCondition))

gg + geom_bar() +
  facet_grid(rows = vars(MoSold), cols = vars(YrSold)) +
  theme(axis.text.x = element_text(angle = 90, hjust = 1))


gg + geom_bar(aes(fill = factor(YrSold)), position = 'dodge')


ggplot(df, aes(x = SoldDate, color = SaleCondition)) +
  geom_freqpoly()


ggplot(df, aes(x = SaleCondition, y = `log(SalePrice)`)) +
  geom_boxplot(position = 'dodge', notch = T, aes(color = factor(YrSold)))

Bearing in mind that 2010 is not a complete year in this set, partial sales dropped as normal sales increased. This trend may be explained by developers finishing/halting their projects. Filtering/grouping by year built and/or neighborhood might help check this, but I’ll skip it for the sake of finishing.

Fall and winter months seemed to be where the bulk of these increases in normal sales fell each year.

Overall Correlations

Back to top.

To wrap up this notebook, and as a preliminary gauge on how well I have prepared the data for ML, mainly for linear regression, I’ll check out how correlations have changed. Have my variables increased in correlation in general? Have they increased in correlation to the target variable?

Increased correlations to the target variable have obvious benefits. Increased correlations between predictor variables will help clarify which variables may overlap in their predictive power and redundantly overweight the same underlying information.

I made a lot of new features, dropping some along the way. In cases where a Winsorized version seemed a good option, I also kept the scale-transformed version where applicable. For example, I kept both log(SalePrice) and Win(log(SalePrice)). Winsorization will help a straightforward linear regression without interactions. But, Winsorization will undercut KNN’s ability to cluster multivariate outliers and similarly RF’s ability to group by extremes. That said, Winsorization will reduce the chances of overfit in all three. All that is to say that there are redundant new features.

I can’t think of a quick and dirty way to view this without getting skewed results or busy heatmaps, other than to make a table of all the variables’ correlations to the target variable which isn’t very easily digested either. The slow and tedious way would be to iteratively “manually” (to some extent) select like variables for correlation within their experimental group. I’m not goin to do that.

val_train_Xy_numeric = select(
  val_train_Xy, # Reorder for easier comparison.
  c('SalePrice', 'log(SalePrice)', 'Win(log(SalePrice))', "LotFrontage",
    "log10(LotFrontage)", "Win(log10(LotFrontage))", "Win(LotFrontage)",
    "LotArea", "log10(log10(LotArea))", "Win(LotArea)", "OverallQual_int",
    "OverallCond_int", "YearBuilt", "sqrt(Age)", "YearRemodAdd", "MasVnrArea",
    "cbrt(MasVnrArea)", "Win(cbrt(MasVnrArea))", "BsmtFinSF1", "BsmtFinSF2",
    "BsmtUnfSF", "cbrt(BsmtUnfSF)", "square(log(TotalBsmtSF))",
    "Win(square(log(TotalBsmtSF)))", "TotalBsmtSF", "TotalBsmtFinSF",
    "sqrt(TotalBsmtFinSF)", "Win(sqrt(TotalBsmtFinSF))", "X2ndFlrSF",
    "X2ndFlr.bin", "GrLivArea", "square(log2(GrLivArea))", 
    "Win(square(log2(GrLivArea)))", "TotBaths", "Win(TotBaths)",
    "BedroomAbvGr", "Win(BedroomAbvGr)", "TotRmsAbvGrd", "Win(BedroomAbvGr)",
    "TotRmsAbvGrd", "Win(TotRmsAbvGrd)", "Fireplaces.bin", "GarageCars",
    "Win(GarageCars)", "WoodDeckSF", "cbrt(WoodDeckSF)",
    "Win(cbrt(WoodDeckSF))", "OpenPorchSF", "OpenPorch.bin", "EnclosedPorch",
    "ScreenPorch", "cbrt(ScreenPorch)", "ScreenPorch.bin", "MiscVal",
    "log(MiscVal)", "Win(log(MiscVal))", "MoSold", "YrSold")
)

ggcorr(val_train_Xy_numeric)


cor_mtx = cor(val_train_Xy_numeric, use = 'pairwise.complete.obs')

target_vars_vec = c('SalePrice', 'log(SalePrice)', 'Win(log(SalePrice))')

cor_mtx_melted = melt(cor_mtx)
sales_cor_mtx_melted = filter(
  cor_mtx_melted,
  Var1 %in% target_vars_vec & !(Var2 %in% target_vars_vec)
)

ggplot(sales_cor_mtx_melted, aes(x = Var1, y = Var2)) +
  geom_tile(aes(fill = value))


dcast(sales_cor_mtx_melted, formula = Var2 ~ Var1)

fenced_jbv(
  data = sales_cor_mtx_melted,
  x = 'Var1',
  y = 'value',
  jit_h = 0
)

Serialize Dataframe for Storage

Back to top.

I’ll write it to a CSV file so I can verify that my final engineering script duplicates this process. I’ll verify in the next notebook before I start modeling.

val_train_Xy$Id = val_train_X$Id

saveRDS(val_train_Xy, 'data/eda_val_train_Xy.rds')
head(readRDS('data/eda_val_train_Xy.rds'))
LS0tDQp0aXRsZTogIkVEQSBhbmQgRmVhdHVyZSBFbmdpbmVlcmluZyBmb3IgSG91c2luZyBQcmljZSBSZWdyZXNzaW9uIg0KYXV0aG9yOiAiS2FsZWIgQ29iZXJseSINCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCICVkLCAlWScpYCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZGVwdGg6IDUNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQotLS0NCg0KPGEgaWQ9InRvcCI+PC9hPg0KDQojIENvbnRpbnVlZCBGcm9tIHdyYW5nbGVfYW5kX3NwbGl0LlJtZA0KDQpbU2VlIHRoZSB1cHN0cmVhbSB3cmFuZ2xlIGFuZCBzcGxpdCBub3RlYm9vayBoZXJlLl0oaHR0cHM6Ly9naXRodWIuY29tL0thbGViQ29iZXJseS9LYWdnbGVfSG91c2luZ19QcmljZV9SZWdyZXNzaW9uL3dyYW5nbGVfYW5kX3NwbGl0Lmh0bWwpe3RhcmdldD0iX2JsYW5rIn0NCg0KW1NlZSB0aGUgcHJvamVjdCBvdmVydmlldyBpbiB0aGUgUkVBRE1FLl0oaHR0cHM6Ly9naXRodWIuY29tL0thbGViQ29iZXJseS9LYWdnbGVfSG91c2luZ19QcmljZV9SZWdyZXNzaW9uL3dyYW5nbGVfYW5kX3NwbGl0Lmh0bWwpe3RhcmdldD0iX2JsYW5rIn0NCg0KIyBEaXZpbmcgSW4NCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KIyMgTGlicmFyaWVzIGFuZCBTb3VyY2UgRmlsZXMNCg0KPGEgaWQ9ImxpYnJhcmllcyI+PC9hPg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCiMgbGlicmFyeShkaWN0KSAjIFN0aWxsIG5vdCBmb3VuZCBhZnRlciBpbnN0YWxsYXRpb24NCmxpYnJhcnkoY29udGFpbmVyKSAjIEZvciBEaWN0IGNsYXNzDQpsaWJyYXJ5KHVzZWZ1bCkgIyBGb3Igc2ltcGxlLmltcHV0ZQ0KbGlicmFyeShjb21wcmVoZW5yKSAjIEZvciBsaXN0IGNvbXByZWhlbnNpb24NCmxpYnJhcnkoR0dhbGx5KQ0KbGlicmFyeShyZXNoYXBlMikNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KbGlicmFyeShncGxvdHMpDQpsaWJyYXJ5KERlc2NUb29scykgIyBGb3IgZGYgc3VtbWFyeQ0KbGlicmFyeShyb2J1c3RIRCkgIyBGb3IgZGYgc3VtbWFyeQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoZWZmc2l6ZSkgIyBGb3IgQ29oZW4ncyBkDQoNCnNvdXJjZSgndG9vbHMvd3JhbmdsZS5SJykNCnNvdXJjZSgndG9vbHMvZWRhLlInKQ0Kc291cmNlKCd0b29scy9lbmdpbmVlci5SJykNCnNvdXJjZSgndG9vbHMvc3BsaXQuUicpDQoNCiMgU0VFRCA9IDY1NDY2DQpgYGANCg0KIyMgTG9hZGluZyBEYXRhDQoNCkhlcmUncyB0aGUgd3JhbmdsZWQgdHJhaW5pbmcgc2V0IGxvYWRlZCBmcm9tIG9iamVjdHMgc2VyaWFsaXplZCBhdCB0aGUgZW5kIG9mIHdyYW5nbGVfYW5kX3NwbGl0LlJtZC4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1ggPSByZWFkUkRTKCJkYXRhL3ZhbF90cmFpbl9YX3dyYW5nbGVkLnJkcyIpDQp2YWxfdHJhaW5feSA9IHJlYWRSRFMoImRhdGEvdmFsX3RyYWluX3lfd3JhbmdsZWQucmRzIikNCg0KIyBNZXJnZSBmb3IgZWFzaWVyIGFuYWx5c2lzLg0KdmFsX3RyYWluX1h5ID0gbWVyZ2UoDQogIHggPSB2YWxfdHJhaW5fWCwNCiAgeSA9IHZhbF90cmFpbl95LA0KICBieSA9ICdJZCcsDQogIGFsbCA9IFRSVUUNCikNCmBgYA0KDQojIEVEQSBhbmQgRW5naW5lZXJpbmcNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc3RyKHZhbF90cmFpbl9YeSkNCmBgYA0KDQoNClRoZXJlIGFyZSA3MTUgdG90YWwgb2JzZXJ2YXRpb25zIGluIHRoZSB2YWxpZGF0aW9uIHRyYWluaW5nIHNldCwgYWNyb3NzIDgxIGZlYXR1cmVzICh0YXJnZXQgZmVhdHVyZSAiU2FsZXByaWNlIiBhbmQgaWQgY29sdW1uICJJZCIgaW5jbHVkZWQpLiA0NiBmZWF0dXJlcyBhcmUgZmFjdG9ycywgMjQgb2Ygd2hpY2ggYXJlIG9yZGVyZWQuIDE0IGFyZSBpbnRlZ2Vycy4gMjAgYXJlIGRvdWJsZXMsIGluY2x1ZGluZyAiU2FsZVByaWNlIi4gKCJJZCIgaXMgaW50ZWdlcnMgY2FzdCBhcyBhIGNoYXJhY3RlciB0eXBlLikNCg0KIyMgQ29ycmVsYXRpb25zDQoNClRoZSBmdWxsIGNvcnJlbGF0aW9uIGdyaWQgaXMgdG9vIGxhcmdlIGZvciBtb3N0IHNjcmVlbnMsIGJ1dCB0aGVyZSBhcmUgb25seSBhIGhhbmRmdWwgb2Ygbm90ZXdvcnRoeSBjb3JyZWxhdGlvbnMgd2hpY2ggSSdsbCBpbmNsdWRlIHdpdGggZnVydGhlciBhbmFseXNpcyBvZiBlYWNoIGZlYXR1cmUuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgZ2djb3JyKA0KIyAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSwNCiMgICBsYWJlbCA9IFQsDQojICAgbGFiZWxfcm91bmQgPSAyLA0KIyAgIGxhYmVsX3NpemUgPSAzDQojICkNCmBgYA0KDQojIyBOb3JtYWxpemluZyBDb250aW51b3VzIFZhcmlhYmxlcw0KDQpJIHdyb3RlIGEgc2ltcGxlIGFsZ29yaXRobSB0byB0cnkgdmFyaW91cyB0cmFuc2Zvcm1hdGlvbnMgb24gZWFjaCBjb250aW51b3VzIGZlYXR1cmUgYW5kIHVzZSB0aGUgW1NoYXBpcm8tV2lsayBOb3JtYWxpdHkgVGVzdF0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3N0YXRzL3ZlcnNpb25zLzMuNi4yL3RvcGljcy9zaGFwaXJvLnRlc3Qpe3RhcmdldD0iX2JsYW5rIn0gdG8gY2hvb3NlIHRoZSBiZXN0IHRyYW5zZm9ybWF0aW9uLiBJIHRoZW4gdmlzdWFsaXplIGVhY2ggZmVhdHVyZSBhbmQgZGVjaWRlIGlmIGl0IGV2ZW4gbWFrZXMgc2Vuc2UgdG8gYXR0ZW1wdCBub3JtYWxpemF0aW9uLg0KDQpJIHNob3VsZCBoYXZlIG1hZGUgbG9nYXJpdGhtaWMgdHJhbnNmb3JtYXRpb25zIGJlIHRoYXQgb2YgeCsxLCBidXQgaW5zdGVhZCBJIGV4Y2x1ZGVkIDBzLCB3aGljaCBvbmx5IHBhcnRpYWxseSBoYW5kbGVkIHRoZSBpc3N1ZS4gMXMgc3RpbGwgY29udmVydCB0byAwcyBpbiB0aGF0IGNhc2UuIFRoZSByZXN1bHQgd2FzIHRoYXQgdmFyaWFibGVzIHdpdGggYSBzdWJzdGFudGlhbCBudW1iZXIgb2YgMXMgZGlkIG5vdCBmaW5kIGxvZ3MgdmVyeSB1c2VmdWwgZm9yIG5vcm1hbGl6YXRpb24uDQoNCkkgYWxzbyBkaWQgbm90IGluY2x1ZGUgbW9yZSBkeW5hbWljIHRyYW5zZm9ybWF0aW9ucyBsaWtlIEJveC1Db3guIFRoZSBzY3JpcHQgY291bGQgYmUgbW9kaWZpZWQgdG8gaW5jbHVkZSB0aGVtLg0KDQpGb3IgdmFyaWFibGVzIGluIHdoaWNoIGEgMCBpbmRpY2F0ZXMgYSBtaXNzaW5nIGZlYXR1cmUsIEkgbm9ybWFsaXplZCBvbmx5IHRoZSBub24temVybyBzZXQuIFRoZSBpZGVhIHdhcyB0aGF0IGl0IG1pZ2h0IGFpZCByZWdyZXNzaW9uIHdoZW4gdGhlIHZhcmlhYmxlIGlzIHB1dCBpbnRvIGludGVyYWN0aW9uIHdpdGggaXRzIG1pc3NpbmduZXNzLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpmdW5jc19sc3QgPSBsaXN0KA0KICAgICdub19mdW5jJyA9IGZ1bmN0aW9uICh4KSB7IHggfSwNCiAgICAnc3FydCcgPSBzcXJ0LA0KICAgICdjYnJ0JyA9IGZ1bmN0aW9uKHgpIHsgeF4oMS8zKSB9LA0KICAgICdzcXVhcmUnID0gZnVuY3Rpb24oeCkgeyB4XjIgfSwNCiMjIw0KIyMjIEZJWE1FDQojIE1ha2UgbG9nIHRyYW5zZm9ybWF0aW9ucyBvZiB4KzEuDQojIyMNCiAgICAnbG9nJyA9IGxvZywNCiAgICAnbG9nMicgPSBsb2cyLA0KICAgICdsb2cxMCcgPSBsb2cxMCwNCiAgICAnMS94JyA9IGZ1bmN0aW9uICh4KSB7IDEveCB9LA0KICAgICcyXigxL3gpJyA9IGZ1bmN0aW9uICh4KSB7IDJeKDEveCkgfQ0KICAgICMgQm94IENveDogd3JpdGUgZnVuY3Rpb24gdGhhdCBjYWxscyBNQVNTOjpib3hjb3ggYW5kIGluY2x1ZGUgbGFtYmRhIGluIHJlc3VsdHMvZnVuY3Rpb24gcmV0dXJuZWQuDQogICAgIyBZZW8tSm9obnNvbg0KICAgICMgV2luc29yaXplIGhlcmU/DQogICAgIyBSYW5rDQogICAgIyBSYW5rLUdhdXNzDQogICkNCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCmJlc3Rfbm9ybWFsaXplcnMgPSBmaW5kX2Jlc3Rfbm9ybWFsaXplcl9wZXJfZmVhdCgNCiAgZGYgPSB2YWxfdHJhaW5fWHksDQogIGZlYXRzX2xzdCA9IG51bV9mZWF0cywNCiAgZnVuY3NfbHN0ID0gZnVuY3NfbHN0LA0KICBleGNsdWRlX3ZhbHMgPSBsaXN0KDApDQopDQoNCnByaW50KCJCZXN0IG5vcm1hbGl6aW5nIHRyYW5zZm9ybWF0aW9uczoiKQ0KZm9yIChmZWF0IGluIG5hbWVzKGJlc3Rfbm9ybWFsaXplcnMpKSB7DQogIGZ1bmNfbmFtZSA9IGJlc3Rfbm9ybWFsaXplcnNbW2ZlYXRdXSRiZXN0X2Z1bmMkbmFtZQ0KICBwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgIGZlYXQsICI6IiwgZnVuY19uYW1lLA0KICAgICAgIiwgcC12YWx1ZToiLCBiZXN0X25vcm1hbGl6ZXJzW1tmZWF0XV0kcmVzdWx0c1tbZnVuY19uYW1lXV0kcC52YWx1ZQ0KICAgICkNCiAgKQ0KfQ0KYGBgDQoNCiMjIFNhbGVQcmljZQ0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQojIyMgTm9ybWFsaXplDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnU2FsZVByaWNlJw0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbW3hdXSkNCg0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSA1MDAwLA0KICB0X2JpbncgPSAxLzUwDQopDQpgYGANCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoJ2xvZyhTYWxlUHJpY2UpJyA9IGxvZyhTYWxlUHJpY2UpKQ0KDQp4ID0gJ2xvZyhTYWxlUHJpY2UpJw0KDQojIFJlY2FsY3VsYXRlIGJlc3Qgbm9ybWFsaXplcnMuDQpudW1fZmVhdHMgPSBjb2xuYW1lcyhzZWxlY3QodmFsX3RyYWluX1h5LCB3aGVyZShpcy5udW1lcmljKSkpDQpiZXN0X25vcm1hbGl6ZXJzID0gZmluZF9iZXN0X25vcm1hbGl6ZXJfcGVyX2ZlYXQoDQogIGRmID0gdmFsX3RyYWluX1h5LA0KICBmZWF0c19sc3QgPSBudW1fZmVhdHMsDQogIGZ1bmNzX2xzdCA9IGZ1bmNzX2xzdCwNCiAgZXhjbHVkZV92YWxzID0gbGlzdCgwKQ0KKQ0KDQpzdW1tYXJ5KHZhbF90cmFpbl9YeVt4XSkNCg0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAxLzEwMCwNCiAgdF9iaW53ID0gMS8xMDANCikNCmBgYA0KDQpBIG5hdHVyYWwgbG9nIGJlc3Qgbm9ybWFsaXplcyB0aGUgc2FsZSBwcmljZSBkaXN0cmlidXRpb24uIEhvd2V2ZXIsIGJlY2F1c2UgaXQgaXNuJ3QgYSBsb2cxMCB0cmFuc2Zvcm1hdGlvbiwgaXQgd29uJ3QgcHJlY2lzZWx5IHNjYWxlIHRoZSBwcmVkaWN0aW9uIGVycm9ycyBwcm9wb3J0aW9uYWxseSB0byB0aGUgc2FsZSBwcmljZS4gSSBjb3VsZCBzaW1wbHkgdXNlIHRoZSBsb2cxMCBpbnN0ZWFkLCB3aGljaCBkb2VzIGEgZmFpciBqb2IgYXQgbm9ybWFsaXppbmcgYXMgd2VsbCwgYnV0IEkgd2FudCB0byBzdGljayB3aXRoIHRoZSAiYmVzdCIgdHJhbnNmb3JtYXRpb24gdG8gbWFrZSB0aGUgYmVzdCBtb2RlbC4gU28sIHdoZW4gSSBydW4gTUwsIEkgd2lsbCB3cml0ZSBhIGN1c3RvbSBzdW1tYXJ5IGZ1bmN0aW9uIHRvIHRyYWluIHdpdGggd2hpY2ggc2ltcGx5IGRpdmlkZXMgdGhlIGVycm9yIGJ5IHRoZSBwcmljZSAodGhlIGxvZyhTYWxlUHJpY2UpKSBiZWZvcmUgY2FsY3VsYXRpbmcgdGhlIFJNU0UuDQoNCiMjIyBXaW5zb3JpemUNCg0KSGVyZSBJJ20gbG9va2luZyBmb3IgdGhlIGJlc3QgV2luc29yaXphdGlvbiBxdWFudGlsZSB2YWx1ZXMgb2YgdGhlIGJlc3QgdHJhbnNmb3JtYXRpb24gLS0gdGhlIGJlc3QgYWNjb3JkaW5nIHRvIFNoYXBpcm8tV2lsayBwLXZhbHVlcy4gSSBjb3VsZCBoYXZlIHByb2dyYW1tYXRpY2FsbHkgZXhwbG9yZWQgdGhlIHNwYWNlIGFuZCByZXR1cm5lZCB0aGUgYmVzdCByZXN1bHQuIEJ1dCwgSSB3YW50IHRvIHZpc3VhbGl6ZSBpdCBhbmQgZXhwbG9yZSB0aGUgcHJvY2VzcyBpdHNlbGYuIEluIGZ1dHVyZSBwcm9qZWN0cywgSSBtaWdodCBjaG9vc2UgdG8gZnVydGhlciBhdXRvbWF0ZSB0aGlzLg0KDQpJdCBzaG91bGQgYmUgbm90ZWQgdGhhdCBXaW5zb3JpemF0aW9uIG9mIHRoZSB0YXJnZXQgdmFyaWFibGUgc2hvdWxkIG9ubHkgYmUgdXNlZCBmb3IgdHJhaW5pbmcsIG5vdCBmb3IgdGVzdGluZy4gQSBsb2cgdHJhbnNmb3JtYXRpb24gY2FuIGJlIHJldmVyc2VkIGFzIGEgdmVjdG9yaXplZCBvcGVyYXRpb24sIGJ1dCBXaW5zb3JpemF0aW9uIGNhbid0LiBXaW5zb3JpemF0aW9uIHNob3VsZCBpbXByb3ZlIHRoZSBhY2N1cmFjeSBvZiB0aGUgbW9kZWwsIGJ1dCB3b3VsZCBiZSBjaGVhdGluZyBvbiB0aGUgdGVzdC4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcXFub3JtKHkgPSB2YWxfdHJhaW5fWHkkU2FsZVByaWNlLCB5bGFiID0gJ1NhbGVQcmljZScpDQpxcWxpbmUoeSA9IHZhbF90cmFpbl9YeSRTYWxlUHJpY2UsIHlsYWIgPSAnU2FsZVByaWNlJykNCg0KcXFub3JtKHkgPSB2YWxfdHJhaW5fWHkkYGxvZyhTYWxlUHJpY2UpYCwgeWxhYiA9ICdsb2coU2FsZVByaWNlKScpDQpxcWxpbmUoeSA9IHZhbF90cmFpbl9YeSRgbG9nKFNhbGVQcmljZSlgLCB5bGFiID0gJ2xvZyhTYWxlUHJpY2UpJykNCg0KV2luX2xvZ194ID0gV2luc29yaXplKA0KICB4ID0gdmFsX3RyYWluX1h5W1snbG9nKFNhbGVQcmljZSknXV0sDQogIHByb2JzID0gYygwLjAwNSwgMC45OTUpDQopDQoNCnFxbm9ybSh5ID0gV2luX2xvZ194LCB5bGFiID0gJ1dpbl9sb2dfeCcpDQpxcWxpbmUoeSA9IFdpbl9sb2dfeCwgeWxhYiA9ICdXaW5fbG9nX3gnKQ0KDQpXaW5fcmF3X3ggPSBXaW5zb3JpemUoDQogIHggPSB2YWxfdHJhaW5fWHlbWydTYWxlUHJpY2UnXV0sDQogIHByb2JzID0gYygwLCAwLjk1KQ0KKQ0KDQpxcW5vcm0oeSA9IFdpbl9yYXdfeCwgeWxhYiA9ICdXaW5fcmF3X3gnKQ0KcXFsaW5lKHkgPSBXaW5fcmF3X3gsIHlsYWIgPSAnV2luX3Jhd194JykNCg0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSB2YWxfdHJhaW5fWHkkU2FsZVByaWNlKSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gdmFsX3RyYWluX1h5JGBsb2coU2FsZVByaWNlKWApKQ0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBXaW5fbG9nX3gpKQ0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBXaW5fcmF3X3gpKQ0KYGBgDQoNCkEgc21hbGwgV2luc29yaXphdGlvbiBvZiB0aGUgbG9nIGJlc3Qgbm9ybWFsaXplcyB0aGUgdmFyaWFibGUgKFcgPSAwLjk5MDYyKS4gSXQgZG9lc24ndCBwYXNzIHRoZSB0ZXN0IGZvciBub3JtYWxpdHkgKHAgPCAwLjAxKSwgYnV0IGl0IGlzIHN0aWxsIGJldHRlciBwcmVwYXJlZCBmb3IgYSBsaW5lYXIgcmVncmVzc2lvbi4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoDQogICAgJ1dpbihTYWxlUHJpY2UpJyA9IFdpbnNvcml6ZSgNCiAgICAgIFNhbGVQcmljZSwNCiAgICAgIHByb2JzID0gYygwLCAwLjk1KSwNCiAgICAgIG5hLnJtID0gVA0KICAgICkNCiAgKSAlPiUNCiAgbXV0YXRlKA0KICAgICdXaW4obG9nKFNhbGVQcmljZSkpJyA9IFdpbnNvcml6ZSgNCiAgICAgIGxvZyhTYWxlUHJpY2UpLA0KICAgICAgcHJvYnMgPSBjKDAuMDA1LCAwLjk5NSksDQogICAgICBuYS5ybSA9IFQNCiAgICApDQogICkNCmBgYA0KDQojIyMgQ29ycmVsYXRpb25zDQoNCkhlcmUgYXJlIHRoZSBjb3JyZWxhdGlvbnMgYmV0d2VlbiBTYWxlUHJpY2UgYW5kIHRoZSByZXN0IG9mIHRoZSB2YXJpYWJsZXMsIGNvbXBhcmVkIHRvIHRob3NlIG9mIHRoZSB0cmFuc2Zvcm1lZCB2YXJpYWJsZXMuIFRyYW5zZm9ybWluZyBTYWxlUHJpY2UgcmVzdWx0ZWQgaW4gbWlub3IgaW5jcmVhc2VzIG9mIGNvcnJlbGF0aW9ucyB0byBtYW55IG90aGVyIGNvbnRpbnVvdXMgZmVhdHVyZXMgYW5kIHNvbWUgbWlub3IgcmVkdWN0aW9ucyBvZiBjb3JyZWxhdGlvbnMsIGFuIG92ZXJhbGwgbWlub3IgYW5kIGluc2lnbmlmaWNhbnQgaW1wcm92ZW1lbnQuIEJ1dCwgdGhpcyBpcyB0aGUgZmlyc3Qgb2YgdGhlIHZhcmlhYmxlcyB0byBiZSB0cmFuc2Zvcm1lZC4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeCA9ICdXaW4obG9nKFNhbGVQcmljZSkpJw0KeF9sc3QgPSBjKCdTYWxlUHJpY2UnLCAnbG9nKFNhbGVQcmljZSknLCAnV2luKGxvZyhTYWxlUHJpY2UpKScsDQogICAgICAgICAgJ1dpbihTYWxlUHJpY2UpJykNCg0KZGYgPSBnZXRfY29ycygNCiAgZGF0YSA9IGZpbHRlcigNCiAgICBzZWxlY3QodmFsX3RyYWluX1h5LCBhbGxfb2YobnVtX2ZlYXRzKSksDQogICAgIWlzLm5hKC5kYXRhW1t4XV0pDQogICksDQogIHhfbHN0ID0geF9sc3QsDQogIGZlYXRzID0gbnVtX2ZlYXRzDQopDQpkZg0KcHJpbnQoIlN1bW1hcnkgb2YgYWJzb2x1dGUgdmFsdWVzIG9mIFBlYXJzb24ncyBSczoiKQ0KZGYgPSBhYnMoZGYpDQpzdW1tYXJ5KGFicyhkZikpDQoNCmRmID0gbWVsdChkZikNCmdncGxvdChkZiwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHZhbHVlKSkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArDQogIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMnKQ0KYGBgDQoNCiMjIyBIYXJkIENvZGUNCg0KSSdsbCBuZWVkIHRvIGhhcmQgY29kZSB0aGUgdG9wIGFuZCBib3R0b20gbGltaXRzIGZvciB0aGUgZW5naW5lZXJpbmcgc2NyaXB0IHRvIGFwcGx5IHRvIHRoZSB0ZXN0IHNldCB3aXRob3V0IGxlYWthZ2UuIEFuZCwgSSdsbCBuZWVkIHRvIGRyb3AgV2luKFNhbGVQcmljZSkuDQoNCkkgd2FudCB0byBrZWVwIHRoZSB0cmFuc2Zvcm1lZCBhbmQgbm9uLVdpbnNvcml6ZWQgdmVyc2lvbiB0aG91Z2guIEluIHRoZSBjYXNlIG9mIHRoZSB0YXJnZXQgdmFyaWFibGUsIEknbGwgdHJhaW4gb24gdGhlIFdpbnNvcml6ZWQgdmFyaWFibGUgYW5kIHRlc3Qgb24gdGhlIHRyYW5zZm9ybWVkIGJlY2F1c2UgSSBjYW4gcmV2ZXJzZSB0aGUgdHJhbnNmb3JtYXRpb24uIEluIHRoZSBjYXNlIG9mIHByZWRpY3RvciB2YXJpYWJsZXMsIHRoZSBXaW5zb3JpemVkIHZlcnNpb24gd2lsbCBiZSBnb29kIGZvciBiYXNpYyBsaW5lYXIgcmVncmVzc2lvbiB3aXRob3V0IGludGVyYWN0aW9ucywgYnV0IG5vdCBuZWNlc3NhcmlseSBmb3IgS05OIGFuZCBSRiB3aGljaCBtYXkgYmUgYWJsZSB0byB1c2UgdGhlIG91dGxpZXJzIGZvciBjbHVzdGVyaW5nIGFuZCBncm91cGluZy4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdXaW4obG9nKFNhbGVQcmljZSkpJw0KDQptaW5fdmFsID0gbWluKHZhbF90cmFpbl9YeVtbeF1dKQ0KbWF4X3ZhbCA9IG1heCh2YWxfdHJhaW5fWHlbW3hdXSkNCnByaW50KHBhc3RlKCJtaW5fdmFsOiIsIG1pbl92YWwpKQ0KcHJpbnQocGFzdGUoIm1heF92YWw6IiwgbWF4X3ZhbCkpDQoNCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKA0KICAgICdXaW4obG9nKFNhbGVQcmljZSkpJyA9IFdpbnNvcml6ZSgNCiAgICAgIC5kYXRhW1snbG9nKFNhbGVQcmljZSknXV0sDQogICAgICBtaW52YWwgPSBtaW5fdmFsLA0KICAgICAgbWF4dmFsID0gbWF4X3ZhbA0KICAgICkNCiAgKSAlPiUNCiAgc2VsZWN0KC1jKCdXaW4oU2FsZVByaWNlKScpKQ0KDQpnZyA9IGdncGxvdCh2YWxfdHJhaW5fWHksIGFlcyh4ID0gLmRhdGFbW3hdXSkpDQpwMSA9IGdnICsgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLzUwKQ0KcDIgPSBnZyArIGdlb21fYm94cGxvdChub3RjaCA9IFQpDQpncmlkLmFycmFuZ2UocDEsIHAyKQ0KYGBgDQoNCiMjIyBTYWxlUHJpY2UgYXMgRmFjdG9yDQoNClRvIGFpZCB2aXN1YWxpemF0aW9uLCBJJ2xsIGNyZWF0ZSBhIFNhbGVQcmljZSBmYWN0b3Igd2l0aCBleHRyZW1lcyBhbmQgcXVhcnRpbGVzIGFzIGxldmVscy4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoDQogICAgJ1NhbGVQcmljZS5mYWN0JyA9IGN1dCgNCiAgICAgIHggPSBTYWxlUHJpY2UsDQogICAgICBicmVha3MgPSBxdWFudGlsZSh4ID0gU2FsZVByaWNlKSwNCiAgICAgIGluY2x1ZGUubG93ZXN0ID0gVCwNCiAgICAgIG9yZGVyZWRfcmVzdWx0ID0gVA0KICAgICkNCiAgKQ0KDQpzdW1tYXJ5KHZhbF90cmFpbl9YeSRTYWxlUHJpY2UuZmFjdCkNCmBgYA0KDQojIyBNU1N1YkNsYXNzIChEd2VsbGluZyBUeXBlKQ0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpJJ2xsIHVzZSBsb2coU2FsZVByaWNlKSB0byB2aXN1YWxpemUgYWdhaW5zdCBmYWN0b3JzLCByYXRoZXIgdGhhbiB0aGUgV2luc29yaXplZCB2ZXJzaW9uLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ01TU3ViQ2xhc3MnDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5KQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQpgYGANCg0KVGhlIG1vc3QgY29tbW9uIGNsYXNzIGlzIDEtc3RvcnkgbmV3ZXIgdGhhbiAxOTQ1ICgyNjcpLCBmb2xsb3dlZCBieSAyLXN0b3J5IG5ld2VyIHRoYW4gMTk0NSAoMTQ5KSBhbmQgMS41LXN0b3J5IGZpbmlzaGVkIGFsbCBhZ2VzICg2NykuIFRoZSBwcmljaWVzdCBjbGFzcyBpcyAyLXN0b3J5IG5ld2VyIHRoYW4gMTk0NSwgdGhvdWdoIHNvbWUgY2xhc3NlcyBhcmUgc28gdW5jb21tb24gdGhhdCBpdCdzIGhhcmQgdG8gc2F5IHRoaXMgY29tcGxldGVseSBjb25maWRlbnRseS4NCg0KVGhpcyBmZWF0dXJlIGlzIGEgbWl4IG9mIGluZm9ybWF0aW9uIG1vc3RseSBjb3ZlcmVkIGJ5IEhvdXNlU3R5bGUsIFllYXJCdWlsdCwgYW5kIHNxdWFyZSBmb290YWdlLiBJdCBtaWdodCBiZSB3b3J0aCBkcm9wcGluZyBpdCB0byBhdm9pZCBvdmVyd2VpZ2h0aW5nIHRoaXMgaW5mbywgYXZvaWQgc3B1cmlvdXMgZml0LCBhbmQgc2tpcCB0aGUgY29tcHV0ZSBjb3N0IG9mIDE2IG9uZS1ob3QgZmVhdHVyZXMuIEFsdGVybmF0aXZlbHksIGRlY29tcG9zaXRpb24gd2l0aCBQQ0EgbWlnaHQgaGVscCBwdWxsIG91dCB0aGUgdW5pcXVlIGluZm9ybWF0aW9uIHJlZ2FyZGluZyBQVUQgaG91c2luZy4NCg0KIyMgTVNab25pbmcNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdNU1pvbmluZycNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KDQpwX3ZhbHMgPSBnZXRfc2lnbmlmX2xldmVscyhkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeiA9IHksIG1pbl9uID0gMzApDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCmBgYA0KDQpNb3N0bHkgcmVzaWRlbnRpYWwgbG93IGRlbnNpdHkgKDU3NCksIHNvbWUgbWVkaXVtIGRlbnNpdHkgKDEwMCksIGZld2VyIGZsb2F0aW5nIHZpbGxhZ2UgcmVzaWRlbnRpYWwgKGZsZXhpYmxlIHpvbmluZywgMzEpLiBQcmVkaWN0aXZlIHBvd2VyIG1heSBiZSBsaW1pdGVkIGR1ZSB0byBsYWNrIG9mIGRpdmVyc2l0eS4gVGhhdCBzYWlkLCBsb3ctZGVuc2l0eSByZXNpZGVudGlhbCBhbmQgZmxvYXRpbmcgdmlsbGFnZSBjbGVhcmx5IHRlbmQgdG8gc2VsbCBmb3IgbW9yZSB0aGFuIG1lZGl1bS1kZW5zaXR5LiBDb25zaWRlciBvbmx5IG9uZS1ob3QgZW5jb2RpbmcgUkwsIFJNLCBhbmQgRlY/DQoNCiANCiMjIExvdEZyb250YWdlDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNCiMjIyBOb3JtYWxpemUNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdMb3RGcm9udGFnZScNCg0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbW3hdXSkNCg0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSA1LA0KICB0X2JpbncgPSAxLzUwDQopDQpgYGANCg0KQSBsb2cxMCBzY2FsZSBjZW50ZXJzIGl0IGJldHRlciAoMTMzIG1pc3NpbmcgdmFsdWVzIGV4Y2x1ZGVkKS4NCg0KTm90ZSB0aGUgZXh0cmVtZSBzcGlrZSAofjY1IG9ic2VydmF0aW9ucykgaW4gbGVmdCBvZiBtb2RlICg3MC03NSBTRikgYXQgNjAgU0YuIEl0IGRvZXNuJ3Qgc2VlbSB0byBiZSBhc3NvY2lhdGVkIHdpdGggYW55IHBhcnRpY3VsYXIgbmVpZ2hib3Job29kIG9yIGxvdCBjb25maWd1cmF0aW9uIG9yIGFueXRoaW5nLCBidXQgcHJvYmFibHkganVzdCBhIGNvbW1vbiB3YXkgdG8gY3V0IGxvdHMuDQoNClRoZSBmZWF0dXJlIGNvdWxkIGJlbmVmaXQgZnJvbSB0b3AvYm90dG9tIGNvZGluZy4NCg0KMTMzIE5Bcy4gQ291bnRlcmludHVpdGl2ZWx5LCBhIGxvd2VyIHByb3BvcnRpb24gb2YgbWlzc2luZyBMb3RGcm9udGFnZXMgYXJlIGluc2lkZSBsb3RzICg1NS8xMjEgaW4gdGhlIE5BIHN1YnNldCB2cy4gNTExLzcxNSBpbiB0aGUgdHJhaW5pbmcgc2V0IFt0aGVzZSBudW1iZXJzIGFyZSBmcm9tIGEgcHJldmlvdXMgc3BsaXQgYW5kIG5vdCBhY2N1cmF0ZSBmb3IgdGhlIGN1cnJlbnQgZGF0YSBzZXRdKSwgd2hlcmVhcyBtYW55IGxvdHMgdGhhdCBieSBkZWZpbml0aW9uIGhhdmUgZnJvbnRhZ2UgKDQ0IGNvcm5lciBsb3RzLCBGUjIsIGFuZCBGUjMpIGFyZSBtaXNzaW5nIGZyb250YWdlIHZhbHVlcy4gQ291bGQgdXNlIExvdEFyZWEsIExvdFNoYXBlLCBMb3RDb25maWcsIGFuZCAoPykgKGFsbCBvZiB3aGljaCBhcmVuJ3QgbWlzc2luZyB2YWx1ZXMpIHRvIG11bHRpdmFyaWF0ZSBpbXB1dGUuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnbG9nMTAoTG90RnJvbnRhZ2UpJw0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoDQogICAgJ2xvZzEwKExvdEZyb250YWdlKScgPSBpZmVsc2UoDQogICAgTG90RnJvbnRhZ2UgPT0gMCwNCiAgICAwLA0KICAgIGxvZzEwKExvdEZyb250YWdlKQ0KICAgICkNCiAgKQ0KDQojIFJlY2FsY3VsYXRlIGJlc3Qgbm9ybWFsaXplcnMuDQpudW1fZmVhdHMgPSBjb2xuYW1lcyhzZWxlY3QodmFsX3RyYWluX1h5LCB3aGVyZShpcy5udW1lcmljKSkpDQpiZXN0X25vcm1hbGl6ZXJzID0gZmluZF9iZXN0X25vcm1hbGl6ZXJfcGVyX2ZlYXQoDQogIGRmID0gdmFsX3RyYWluX1h5LA0KICBmZWF0c19sc3QgPSBudW1fZmVhdHMsDQogIGZ1bmNzX2xzdCA9IGZ1bmNzX2xzdCwNCiAgZXhjbHVkZV92YWxzID0gbGlzdCgwKQ0KKQ0KDQpzdW1tYXJ5KHZhbF90cmFpbl9YeVt4XSkNCg0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAxLzUwLA0KICB0X2JpbncgPSAxLzUwDQopDQpgYGANCg0KIyMjIFdpbnNvcml6ZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpxcW5vcm0oeSA9IHZhbF90cmFpbl9YeSRMb3RGcm9udGFnZSwgeWxhYiA9ICdMb3RGcm9udGFnZScpDQpxcWxpbmUoeSA9IHZhbF90cmFpbl9YeSRMb3RGcm9udGFnZSwgeWxhYiA9ICdMb3RGcm9udGFnZScpDQoNCnFxbm9ybSh5ID0gdmFsX3RyYWluX1h5W1t4XV0sIHlsYWIgPSB4KQ0KcXFsaW5lKHkgPSB2YWxfdHJhaW5fWHlbW3hdXSwgeWxhYiA9IHgpDQoNCldpbl9sb2cxMF94ID0gV2luc29yaXplKA0KICB4ID0gdmFsX3RyYWluX1h5W1t4XV0sDQogIHByb2JzID0gYygwLjA1LCAwLjk5KSwNCiAgbmEucm0gPSBUDQopDQoNCnFxbm9ybSh5ID0gV2luX2xvZzEwX3gsIHlsYWIgPSAnV2luX2xvZzEwX3gnKQ0KcXFsaW5lKHkgPSBXaW5fbG9nMTBfeCwgeWxhYiA9ICdXaW5fbG9nMTBfeCcpDQoNCldpbl9yYXdfeCA9IFdpbnNvcml6ZSgNCiAgeCA9IHZhbF90cmFpbl9YeSRMb3RGcm9udGFnZSwNCiAgcHJvYnMgPSBjKDAuMDUsIDAuOTUpLA0KICBuYS5ybSA9IFQNCikNCg0KcXFub3JtKHkgPSBXaW5fcmF3X3gsIHlsYWIgPSAnV2luKExvdEZyb250YWdlKScpDQpxcWxpbmUoeSA9IFdpbl9yYXdfeCwgeWxhYiA9ICdXaW4oTG90RnJvbnRhZ2UpJykNCg0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSB2YWxfdHJhaW5fWHkkTG90RnJvbnRhZ2UpKQ0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSB2YWxfdHJhaW5fWHlbW3hdXSkpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IFdpbl9sb2cxMF94KSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gV2luX3Jhd194KSkNCmBgYA0KDQpJdCBsb29rcyBsaWtlIGp1c3QgV2luc29yaXppbmcgdGhlIHJhdyB2YXJpYWJsZSBtYXkgYmUgdGhlIHdheSB0byBnbyBoZXJlLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgNCiAgICAnV2luKExvdEZyb250YWdlKScgPSBXaW5zb3JpemUoDQogICAgICBMb3RGcm9udGFnZSwNCiAgICAgIHByb2JzID0gYygwLjA1LCAwLjk1KSwNCiAgICAgIG5hLnJtID0gVA0KICAgICAgKQ0KICAgICkgJT4lDQogIG11dGF0ZSgNCiAgICAnV2luKGxvZzEwKExvdEZyb250YWdlKSknID0gV2luc29yaXplKA0KICAgICAgbG9nMTAoTG90RnJvbnRhZ2UpLA0KICAgICAgcHJvYnMgPSBjKDAuMDUsIDAuOTkpLA0KICAgICAgbmEucm0gPSBUDQogICAgICApDQogICAgKQ0KYGBgDQoNCiMjIyBDb3JyZWxhdGlvbnMNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdXaW4oTG90RnJvbnRhZ2UpJw0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeF9sc3QgPSBjKCdMb3RGcm9udGFnZScsICdsb2cxMChMb3RGcm9udGFnZSknLCAnV2luKGxvZzEwKExvdEZyb250YWdlKSknLCAnV2luKExvdEZyb250YWdlKScpDQoNCmRmID0gZ2V0X2NvcnMoDQogIGRhdGEgPSBmaWx0ZXIoDQogICAgc2VsZWN0KHZhbF90cmFpbl9YeSwgYWxsX29mKG51bV9mZWF0cykpLA0KICAgICFpcy5uYSguZGF0YVtbeF1dKQ0KICApLA0KICB4X2xzdCA9IHhfbHN0LA0KICBmZWF0cyA9IG51bV9mZWF0cw0KKQ0KZGYNCnByaW50KCJTdW1tYXJ5IG9mIGFic29sdXRlIHZhbHVlcyBvZiBQZWFyc29uJ3MgUnM6IikNCmRmID0gYWJzKGRmKQ0Kc3VtbWFyeShhYnMoZGYpKQ0KDQpkZiA9IG1lbHQoZGYpDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gdmFyaWFibGUsIHkgPSB2YWx1ZSkpICsNCiAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKw0KICB5bGFiKGxhYmVsID0gJ0Fic29sdXRlIFZhbHVlIG9mIENvcnJlbGF0aW9uIHRvIE90aGVyIEZlYXR1cmVzJykNCg0KeV9sc3QgPSBjKCdsb2coU2FsZVByaWNlKScpDQpmb3IgKGZlYXQgaW4geF9sc3QpIHsNCiAgcGxvdF9zY2F0X3BhaXJzKGRmID0gdmFsX3RyYWluX1h5LCB4ID0gZmVhdCwgeV9sc3QgPSB5X2xzdCkNCn0NCmBgYA0KDQojIyMgSGFyZCBDb2RlDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnV2luKExvdEZyb250YWdlKScNCg0KbWluX3ZhbCA9IG1pbih2YWxfdHJhaW5fWHlbIWlzLm5hKHZhbF90cmFpbl9YeVtbeF1dKSwgeF0pDQptYXhfdmFsID0gbWF4KHZhbF90cmFpbl9YeVshaXMubmEodmFsX3RyYWluX1h5W1t4XV0pLCB4XSkNCnByaW50KHBhc3RlKCJtaW5fdmFsOiIsIG1pbl92YWwpKQ0KcHJpbnQocGFzdGUoIm1heF92YWw6IiwgbWF4X3ZhbCkpDQoNCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKA0KICAgICdXaW4oTG90RnJvbnRhZ2UpJyA9IFdpbnNvcml6ZSgNCiAgICAgIExvdEZyb250YWdlLA0KICAgICAgbWludmFsID0gbWluX3ZhbCwNCiAgICAgIG1heHZhbCA9IG1heF92YWwNCiAgICApDQogICkNCg0KZ2cgPSBnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IC5kYXRhW1t4XV0pKQ0KcDEgPSBnZyArIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSkNCnAyID0gZ2cgKyBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKQ0KZ3JpZC5hcnJhbmdlKHAxLCBwMikNCmBgYA0KDQojIyMgQnkgRmFjdG9ycw0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp5X2xzdCA9IGMoJ01TU3ViQ2xhc3MnLCAnTVNab25pbmcnLCAnTG90U2hhcGUnLCAnTG90Q29uZmlnJywgJ05laWdoYm9yaG9vZCcsDQogICAgICAgICAgJ0JsZGdUeXBlJywgJ0hvdXNlU3R5bGUnKQ0KZm9yICh5IGluIHlfbHN0KSB7DQogIHBsdCA9IGZlbmNlZF9qYnYoDQogICAgZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHksIHkgPSAnbG9nMTAoTG90RnJvbnRhZ2UpJykgKw0KICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQogIHByaW50KHBsdCkNCn0NCmBgYA0KDQpPdmVyYWxsLCB0aGUgY2x1c3RlcnMgb2YgbG90cyBhdCA2MCcgYW5kIDgwJyBpcyBxdWl0ZSBhcHBhcmVudC4NCg0KVW5zdXJwcmlzaW5nbHksIGxvdy1kZW5zaXR5IHJlc2lkZW50aWFsIHRlbmRzIHRvIGhhdmUgbW9yZSBsb3QgZnJvbnRhZ2UgdGhhbiBtZWRpdW0tZGVuc2l0eSByZXNpZGVudGlhbC4gU2xpZ2h0bHkgaXJyZWd1bGFyIGxvdHMgbWlnaHQgdGVuZCB0byBoYXZlIG1vcmUgZnJvbnRhZ2UgdGhhbiByZWd1bGFyLCBidXQgd2UgY2FuJ3Qgc2F5IHRoYXQgd2l0aCBtdWNoIGNvbmZpZGVuY2UuIENvcm5lciBsb3RzIGhhdmUgbW9yZSBmcm9udGFnZSB0aGFuIGluc2lkZSBsb3RzLiBUaGVyZSdzIHF1aXRlIGEgYml0IG9mIHZhcmlhdGlvbiBiZXR3ZWVuIG5laWdoYm9yaG9vZHMuDQoNCkxvb2tpbmcgYXQgTVNTdWJDbGFzcywgb2xkZXIgaG9tZXMgdGVuZCB0byBoYXZlIGxlc3MgbG90IGZyb250YWdlIHRoYW4gdGhlaXIgZXF1aXZhbGVudCBob3VzZSBzdHlsZXMgYWZ0ZXIgMTk0NSwgdW5sZXNzIHRoZXkgYXJlIFBVRCBob21lcywgd2hpY2ggdGVuZCB0byBoYXZlIG11Y2ggbGVzcyBmcm9udGFnZSB0aGFuIHRoZSByZXN0IG9mIHRoZSBjbGFzc2VzLiBUaGlzIGNvbm5lY3Rpb24gaXMgbm90IHZpc2libGUgaW4gWWVhckJ1aWx0LCB3aGljaCBoYXMgbm8gY29ycmVsYXRpb24gdG8gTG90RnJvbnRhZ2UuDQoNClRoZXJlJ3MgYWxzbyBhbiBpbnRlcmVzdGluZyBnYXAgYmV0d2VlbiA1MCcgYW5kIDYwJyB0aGF0IG9ubHkgaG9tZXMgb2xkZXIgdGhhbiAxOTQ1IHRlbmQgdG8gZmlsbCwgZXhjZXB0IGZvciBQVUQgaG9tZXMuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmZlbmNlZF9qYnYoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHksDQogIHggPSAnTVNTdWJDbGFzcycsDQogIHkgPSAnbG9nMTAoTG90RnJvbnRhZ2UpJywNCiAgaml0X2NvbCA9ICdTYWxlUHJpY2UuZmFjdCcsDQogIGxlZ19sYmwgPSAnU2FsZVByaWNlJywNCiAgaml0X2FscGhhID0gMC41LA0KICBib3hfY29sb3IgPSAncmVkJw0KKQ0KDQpnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IGBsb2cxMChMb3RGcm9udGFnZSlgLCB5ID0gYGxvZyhTYWxlUHJpY2UpYCkpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nKSArDQogIGZhY2V0X3dyYXAodmFycyhNU1N1YkNsYXNzKSwgbmNvbCA9IDUpDQpgYGANCg0KIyMgTG90QXJlYSANCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KIyMjIE5vcm1hbGl6ZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ0xvdEFyZWEnDQpzdW1tYXJ5KHZhbF90cmFpbl9YeVtbeF1dKQ0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAyMDAsDQogIHRfYmludyA9IDEvNTANCikNCmBgYA0KDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnhfdHJhbnMgPSAnbG9nMTAoTG90QXJlYSknDQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgnbG9nMTAoTG90QXJlYSknID0gbG9nMTAoTG90QXJlYSkpDQoNCiMgUmVjYWxjdWxhdGUgYmVzdCBub3JtYWxpemVycy4NCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCmJlc3Rfbm9ybWFsaXplcnMgPSBmaW5kX2Jlc3Rfbm9ybWFsaXplcl9wZXJfZmVhdCgNCiAgZGYgPSB2YWxfdHJhaW5fWHksDQogIGZlYXRzX2xzdCA9IG51bV9mZWF0cywNCiAgZnVuY3NfbHN0ID0gZnVuY3NfbHN0LA0KICBleGNsdWRlX3ZhbHMgPSBsaXN0KDApDQopDQoNCnN1bW1hcnkodmFsX3RyYWluX1h5W3hfdHJhbnNdKQ0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0geF90cmFucywNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAxLzUwLA0KICB0X2JpbncgPSAxLzUwMA0KKQ0KYGBgDQoNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeF90cmFucyA9ICdsb2cxMChsb2cxMChMb3RBcmVhKSknDQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgnbG9nMTAobG9nMTAoTG90QXJlYSkpJyA9IGxvZzEwKGxvZzEwKExvdEFyZWEpKSkNCg0KIyBSZWNhbGN1bGF0ZSBiZXN0IG5vcm1hbGl6ZXJzLg0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KYmVzdF9ub3JtYWxpemVycyA9IGZpbmRfYmVzdF9ub3JtYWxpemVyX3Blcl9mZWF0KA0KICBkZiA9IHZhbF90cmFpbl9YeSwNCiAgZmVhdHNfbHN0ID0gbnVtX2ZlYXRzLA0KICBmdW5jc19sc3QgPSBmdW5jc19sc3QsDQogIGV4Y2x1ZGVfdmFscyA9IGxpc3QoMCkNCikNCg0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbeF90cmFuc10pDQpzdW1fYW5kX3RyYW5zX2NvbnQoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHksDQogIHggPSB4X3RyYW5zLA0KICBmdW5jID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRmdW5jLA0KICBmdW5jX25hbWUgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJG5hbWUsDQogIHhfYmludyA9IDEvNTAwLA0KICB0X2JpbncgPSAxLzc1MA0KKQ0KYGBgDQoNCkkgZG91YnQgaXQncyB3b3J0aCBkb2luZyB0aGUgdGhpcmQgbG9nMTAgdHJhbnNmb3JtYXRpb24gbm93IHRoYXQgdGhlIG1lZGlhbiBhbmQgbWVhbiBhcmUgc28gY2xvc2UuIEl0IHN0aWxsIG5lZWRzIHRvcC0gYW5kIGJvdHRvbS1jb2RpbmcgYW55d2F5Lg0KDQpFdmVuIHRoZSBzZWNvbmQgdHJhbnNmb3JtYXRpb24gbWlnaHQgbGVhZCB0byBvdmVyZml0LCBidXQgSSdsbCByb2xsIHdpdGggaXQuDQoNCiMjIyBXaW5zb3JpemUNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcXFub3JtKHkgPSB2YWxfdHJhaW5fWHkkTG90QXJlYSwgeWxhYiA9ICdMb3RBcmVhJykNCnFxbGluZSh5ID0gdmFsX3RyYWluX1h5JExvdEFyZWEsIHlsYWIgPSAnTG90QXJlYScpDQoNCnFxbm9ybSh5ID0gdmFsX3RyYWluX1h5JGBsb2cxMChMb3RBcmVhKWAsIHlsYWIgPSAnbG9nMTAoTG90QXJlYSknKQ0KcXFsaW5lKHkgPSB2YWxfdHJhaW5fWHkkYGxvZzEwKExvdEFyZWEpYCwgeWxhYiA9ICdsb2cxMChMb3RBcmVhKScpDQoNCnFxbm9ybSgNCiAgeSA9IHZhbF90cmFpbl9YeSRgbG9nMTAobG9nMTAoTG90QXJlYSkpYCwNCiAgeWxhYiA9ICdsb2cxMChsb2cxMChMb3RBcmVhKSknDQopDQpxcWxpbmUoDQogIHkgPSB2YWxfdHJhaW5fWHkkYGxvZzEwKGxvZzEwKExvdEFyZWEpKWAsDQogIHlsYWIgPSAnbG9nMTAobG9nMTAoTG90QXJlYSkpJw0KKQ0KDQpXaW5fbG9nMTBsb2cxMF94ID0gV2luc29yaXplKA0KICB4ID0gdmFsX3RyYWluX1h5JGBsb2cxMChsb2cxMChMb3RBcmVhKSlgLA0KICBwcm9icyA9IGMoMC4wNSwgMC45OSksDQogIG5hLnJtID0gVA0KKQ0KDQpxcW5vcm0oeSA9IFdpbl9sb2cxMGxvZzEwX3gsIHlsYWIgPSAnV2luKGxvZzEwKGxvZzEwKExvdEFyZWEpKSknKQ0KcXFsaW5lKHkgPSBXaW5fbG9nMTBsb2cxMF94LCB5bGFiID0gJ1dpbihsb2cxMChsb2cxMChMb3RBcmVhKSkpJykNCg0KV2luX2xvZzEwX3ggPSBXaW5zb3JpemUoDQogIHggPSB2YWxfdHJhaW5fWHkkYGxvZzEwKExvdEFyZWEpYCwNCiAgcHJvYnMgPSBjKDAuMDUsIDAuOTkpLA0KICBuYS5ybSA9IFQNCikNCg0KcXFub3JtKHkgPSBXaW5fbG9nMTBfeCwgeWxhYiA9ICdXaW4obG9nMTAoTG90QXJlYSkpJykNCnFxbGluZSh5ID0gV2luX2xvZzEwX3gsIHlsYWIgPSAnV2luKGxvZzEwKExvdEFyZWEpKScpDQoNCldpbl9yYXdfeCA9IFdpbnNvcml6ZSgNCiAgeCA9IHZhbF90cmFpbl9YeSRMb3RBcmVhLA0KICBwcm9icyA9IGMoMC4wMSwgMC45NSksDQogIG5hLnJtID0gVA0KKQ0KDQpxcW5vcm0oeSA9IFdpbl9yYXdfeCwgeWxhYiA9ICdMb3RBcmVhJykNCnFxbGluZSh5ID0gV2luX3Jhd194LCB5bGFiID0gJ0xvdEFyZWEnKQ0KDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IHZhbF90cmFpbl9YeSRMb3RBcmVhKSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gdmFsX3RyYWluX1h5JGBsb2cxMChMb3RBcmVhKWApKQ0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSB2YWxfdHJhaW5fWHkkYGxvZzEwKGxvZzEwKExvdEFyZWEpKWApKQ0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBXaW5fbG9nMTBsb2cxMF94KSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gV2luX2xvZzEwX3gpKQ0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBXaW5fcmF3X3gpKQ0KYGBgDQoNCkl0IGxvb2tzIGxpa2Ugc2ltcGx5IFdpbnNvcml6aW5nIHRoZSBiYXNlIHZhcmlhYmxlIG1pZ2h0IGJlIGJlc3QuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKA0KICAgICdXaW4oTG90QXJlYSknID0gV2luc29yaXplKA0KICAgICAgTG90QXJlYSwNCiAgICAgIHByb2JzID0gYygwLjAxLCAwLjk1KSwNCiAgICAgIG5hLnJtID0gVA0KICAgICAgKQ0KICAgICkgJT4lDQogIG11dGF0ZSgNCiAgICAnV2luKGxvZzEwKExvdEFyZWEpKScgPSBXaW5zb3JpemUoDQogICAgICBsb2cxMChMb3RBcmVhKSwNCiAgICAgIHByb2JzID0gYygwLjA1LCAwLjk5KSwNCiAgICAgIG5hLnJtID0gVA0KICAgICAgKQ0KICAgICkNCmBgYA0KDQojIyMgQ29ycmVsYXRpb25zDQoNClRyYW5zZm9ybWluZyBMb3RBcmVhIHdpdGggbG9nMTAgcmVzdWx0ZWQgaW4gYmlnZ2VyIHN3aW5ncyBpbiByIGluIGJvdGggZGlyZWN0aW9ucywgYnV0IG5vIHJlYWwgY2hhbmdlIGluIGFnZ3JlZ2F0ZS4gVGhlIGFkZGl0aW9uYWwgbG9nMTAgdHJhbnNmb3JtYXRpb24gcHJvZHVjZWQgbWlub3IgY2hhbmdlcyBpbiBjb3JyZWxhdGlvbiBjb21wYXJlZCB0byB0aGUgaW5pdGlhbCB0cmFuc2Zvcm1hdGlvbiwgbW9zdGx5IHRvd2FyZCBsZXNzIGNvcnJlbGF0aW9uLg0KDQpVbnN1cnByaXNpbmdseSwgdHJhbnNmb3JtZWQgTG90QXJlYSBpcyBtdWNoIG1vcmUgY29ycmVsYXRlZCB0byB0cmFuc2Zvcm1lZCBMb3RGcm9udGFnZSB0aGFuIHRoZWlyIHVudHJhbnNmb3JtZWQgY291bnRlcnBhcnRzIChyIHdlbnQgZnJvbSAwLjUxIHRvIDAuNzMpLiBBbHNvLCB0cmFuc2Zvcm1lZCBMb3RBcmVhIGFuZCB0cmFuc2Zvcm1lZCBTYWxlUHJpY2UgY29ycmVsYXRlIGEgbGl0dGxlIGJldHRlciB0aGFuIHRoZWlyIHVudHJhbnNmb3JtZWQgY291bnRlcnBhcnRzLCBidXQgYXJlIHN0aWxsIHdlYWtseSBjb3JyZWxhdGVkIChyIGluY3JlYXNlZCBmcm9tIDAuMzAgdG8gMC4zNykuDQoNCkl0IGFsc28gYmVjYW1lIG5vdGljZWFibHkgbW9yZSBjb3JyZWxhdGVkIHRvIHNxdWFyZSBmb290YWdlIG9mIHRoZSBmaXJzdCBmbG9vciwgYmVkcm9vbXMgLyB0b3RhbCByb29tcyBhYm92ZSBncm91bmQsIGFuZCBnYXJhZ2UgYXJlYS9jYXJzLiBMaWtlIHByZXZpb3VzIHRyYW5zZm9ybWF0aW9ucywgc29tZSBjb3JyZWxhdGlvbnMgbGVzc2VuZWQgd2l0aCB0aGlzIHRyYW5zZm9ybWF0aW9uLCBidXQgbm9uZSBzbyBtdWNoIHRoYXQgdGhlIGNvcnJlbGF0aW9uIGRyb3BwZWQgYSBicmFja2V0LCBlLmcuIGZyb20gd2VhayB0byBpbnNpZ25pZmljYW50Lg0KDQpUaGUgZGlzdHJpYnV0aW9uIG9mIGNvcnJlbGF0aW9ucyBkcm9wcGVkIHdpdGggdGhlIHNlY29uZCB0cmFuc2Zvcm1hdGlvbi4gSSdsbCBvbmx5IHVzZSB0aGUgZmlyc3QgdHJhbnNmb3JtYXRpb24gaW4gdGhlIGVuZ2luZWVyaW5nIHNjcmlwdC4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeF9sc3QgPSBjKCdMb3RBcmVhJywgJ2xvZzEwKExvdEFyZWEpJywgJ2xvZzEwKGxvZzEwKExvdEFyZWEpKScsICdXaW4obG9nMTAoTG90QXJlYSkpJywgJ1dpbihMb3RBcmVhKScpDQoNCnggPSAnV2luKExvdEFyZWEpJw0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAhaXMubmEoLmRhdGFbW3hdXSkNCiAgKSwNCiAgeF9sc3QgPSB4X2xzdCwNCiAgZmVhdHMgPSBudW1fZmVhdHMNCikNCmRmDQpwcmludCgiU3VtbWFyeSBvZiBhYnNvbHV0ZSB2YWx1ZXMgb2YgUGVhcnNvbidzIFJzOiIpDQpkZiA9IGFicyhkZikNCnN1bW1hcnkoZGYpDQoNCmRmID0gbWVsdChkZikNCmdncGxvdChkZiwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHZhbHVlKSkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArDQogIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMnKQ0KDQp5X2xzdCA9IGMoJ2xvZyhTYWxlUHJpY2UpJykNCmZvciAoZmVhdCBpbiB4X2xzdCkgew0KICBwbG90X3NjYXRfcGFpcnMoZGYgPSB2YWxfdHJhaW5fWHksIHggPSBmZWF0LCB5X2xzdCA9IHlfbHN0KQ0KfQ0KYGBgDQoNCiMjIyBIYXJkIENvZGUNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdXaW4oTG90QXJlYSknDQoNCm1pbl92YWwgPSBtaW4odmFsX3RyYWluX1h5WyFpcy5uYSh2YWxfdHJhaW5fWHlbW3hdXSksIHhdKQ0KbWF4X3ZhbCA9IG1heCh2YWxfdHJhaW5fWHlbIWlzLm5hKHZhbF90cmFpbl9YeVtbeF1dKSwgeF0pDQpwcmludChwYXN0ZSgibWluX3ZhbDoiLCBtaW5fdmFsKSkNCnByaW50KHBhc3RlKCJtYXhfdmFsOiIsIG1heF92YWwpKQ0KDQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgNCiAgICAnV2luKExvdEFyZWEpJyA9IFdpbnNvcml6ZSgNCiAgICAgIExvdEFyZWEsDQogICAgICBtaW52YWwgPSBtaW5fdmFsLA0KICAgICAgbWF4dmFsID0gbWF4X3ZhbA0KICAgICkNCiAgKSAlPiUNCiAgc2VsZWN0KC1jKCdsb2cxMChMb3RBcmVhKScsICdXaW4obG9nMTAoTG90QXJlYSkpJykpDQoNCmdnID0gZ2dwbG90KHZhbF90cmFpbl9YeSwgYWVzKHggPSAuZGF0YVtbeF1dKSkNCnAxID0gZ2cgKyBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEwMCkNCnAyID0gZ2cgKyBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKQ0KZ3JpZC5hcnJhbmdlKHAxLCBwMikNCmBgYA0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp5X2xzdCA9IGMoJ2xvZyhTYWxlUHJpY2UpJywgJ1dpbihMb3RGcm9udGFnZSknLCAnWDFzdEZsclNGJywNCiAgICAgICAgICAnR3JMaXZBcmVhJywnVG90Um1zQWJ2R3JkJywgJ0dhcmFnZUFyZWEnKQ0KeCA9ICdXaW4oTG90QXJlYSknDQpwbG90X3NjYXRfcGFpcnMoZGYgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5X2xzdCA9IHlfbHN0KQ0KYGBgDQoNCiMjIyBCeSBGYWN0b3JzDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnlfbHN0ID0gYygnTVNTdWJDbGFzcycsICdNU1pvbmluZycsICdMb3RTaGFwZScsICdMb3RDb25maWcnLCAnTmVpZ2hib3Job29kJywNCiAgICAgICAgICAnQmxkZ1R5cGUnLCAnSG91c2VTdHlsZScpDQpmb3IgKHkgaW4geV9sc3QpIHsNCiAgcGx0ID0gZmVuY2VkX2pidigNCiAgICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICAgIHggPSB5LA0KICAgIHkgPSAnbG9nMTAobG9nMTAoTG90QXJlYSkpJywNCiAgICBqaXRfaCA9IDAgIyBBZ2FpbiBSIHJhbmRvbWx5IGRlY2lkZXMgdG8gZ28gd29uayB1bmxlc3MgSSBlbnRlciB0aGUgZGVmYXVsdC4NCiAgKSArDQogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3Q9MSkpDQogIHByaW50KHBsdCkNCn0NCmBgYA0KDQpUaGVyZSBhcmUgc2ltaWxhciBwYXR0ZXJucyBhcyBpbiBMb3RGcm9udGFnZSwgd2l0aCBhIG1vcmUgbWFya2VkIHVwd2FyZCB0cmVuZCBhZ2FpbnN0IExvdFNoYXBlLCBhbmQgd2l0aCBsZXNzIGRpZmZlcmVuY2UgYmV0d2VlbiBsb3QgY29uZmlndXJhdGlvbnMuIFRoZXJlJ3MgYWxzbyBhbiBpbnRlcmVzdGluZyBwb2NrZXQgb2YgdHdvLXN0b3J5IGhvdXNlcyB3aXRoIGxvdyBsb3QgYXJlYTsgaXQncyBub3Qgd29ydGggcGxvdHRpbmcsIGJ1dCB5b3UgY2FuIHNlZSB3aGljaCBuZWlnaGJvcmhvb2RzIHRoZXNlIGFyZS4NCg0KIyMgU3RyZWV0DQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNClJlYWxseSBsb3BzaWRlZCB0byBwYXZlZCAoMyBncmF2ZWwsIDcxMiBwYXZlZCkuIERyb3AgdGhpcyBmZWF0dXJlLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzdW1tYXJ5KHZhbF90cmFpbl9YeSRTdHJlZXQpDQp2YWxfdHJhaW5fWHkgPSBzZWxlY3QodmFsX3RyYWluX1h5LCAtYygnU3RyZWV0JykpDQpgYGANCg0KIyMgQWxsZXkNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KVmFzdCBtYWpvcml0eSBoYXZlIG5vbmUsIGRyb3AgaXQuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnN1bW1hcnkodmFsX3RyYWluX1h5JEFsbGV5KQ0KdmFsX3RyYWluX1h5ID0gc2VsZWN0KHZhbF90cmFpbl9YeSwgLWMoJ0FsbGV5JykpDQpgYGANCg0KIyMgTG90U2hhcGUNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdMb3RTaGFwZScNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KDQpwX3ZhbHMgPSBnZXRfc2lnbmlmX2xldmVscyhkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeiA9IHkpDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCmBgYA0KDQpJcnJlZ3VsYXJseSBzaGFwZWQgbG90cyB0ZW5kIHRvIHNlbGwgZm9yIG1vcmUuIEdvb2QgY2FuZGlkYXRlIGZvciBiaW5hcml6YXRpb24gaWYgZG9pbmcgYSBiYXNpYyBsaW5lYXIgcmVncmVzc2lvbiB3aXRoIG5vIGludGVyYWN0aW9uczsgb25lLWhvdCBlbmNvZGUgJ1JlZycgYW5kIGRyb3AgdGhlIHJlc3Qgb2YgdGhlIGxldmVscy4NCg0KIyMgTGFuZENvbnRvdXINCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc3VtbWFyeSh2YWxfdHJhaW5fWHkkTGFuZENvbnRvdXIpDQp2YWxfdHJhaW5fWHkgPSBzZWxlY3QodmFsX3RyYWluX1h5LCAtYygnTGFuZENvbnRvdXInKSkNCmBgYA0KDQojIyBVdGlsaXRpZXMNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KQWxsIGFsbC1wdWJsaWMuIERlZmluaXRlbHkgZHJvcC4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc3VtbWFyeSh2YWxfdHJhaW5fWHkkVXRpbGl0aWVzKQ0KdmFsX3RyYWluX1h5ID0gc2VsZWN0KHZhbF90cmFpbl9YeSwgLWMoJ1V0aWxpdGllcycpKQ0KYGBgDQoNCiMjIExvdENvbmZpZw0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ0xvdENvbmZpZycNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KDQpwX3ZhbHMgPSBnZXRfc2lnbmlmX2xldmVscyhkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeiA9IHkpDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCmBgYA0KDQpUaGlzIHNldCBpcyBtb3N0bHkgaW5zaWRlIGxvdHMgKDUxMykgYnV0IHBsZW50eSBvZiBjb3JuZXIgbG90cyAoMTI0KS4gSSd2ZSBhbHdheXMgdGhvdWdodCBjb3JuZXIgbG90cyBhcmUgcHJpemVkLCBidXQgaXQgZG9lc24ndCBzZWVtIHRvIHNpZ25pZmljYW50bHkgYWRkIHRvIHByaWNlIGNvbXBhcmVkIHRvIGFuIGluc2lkZSBsb3QuDQoNCkN1bCBkZSBzYWNzIGFyZSB0aGUgcHJpY2llc3QuIFRoaXMgaXMgc29tZXdoYXQgYSBwcm94eSBmb3IgbmVpZ2hib3Job29kIChhbmQgbWF5YmUgb3RoZXIgZmVhdHVyZXMpIGFzIGl0IGlzIHRydWUgYWNyb3NzIG1vc3QgbmVpZ2hib3Job29kcyBleGNlcHQgdGhvc2UgdGhhdCBhcmUgYWxyZWFkeSBwcmljZXkgKHdoZXJlIGN1bCBkZSBzYWNzIGFyZSByZWxhdGl2ZWx5IG1vcmUgY29tbW9uKSBvciBsZWFzdCBwcmljZXkgKHdoZXJlIGN1bCBkZSBzYWNzIGRvbid0IHR5cGljYWxseSBleGlzdCkuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmdncGxvdCgNCiAgZGF0YSA9IHZhbF90cmFpbl9YeSwNCiAgIyBkYXRhID0gZmlsdGVyKA0KICAjICAgdmFsX3RyYWluX1h5LA0KICAjICAgTG90Q29uZmlnICVpbiUgYygnSW5zaWRlJywgJ0Nvcm5lcicsICdDdWxEU2FjJykNCiAgIyApLA0KICBtYXBwaW5nID0gYWVzKA0KICAgIHggPSBOZWlnaGJvcmhvb2QsDQogICAgeSA9IGBsb2coU2FsZVByaWNlKWAsDQogICkNCikgKw0KICBnZW9tX2ppdHRlcigNCiAgICBhbHBoYSA9IC40LA0KICAgIG1hcHBpbmcgPSBhZXMoY29sb3IgPSBMb3RDb25maWcsIHNoYXBlID0gTG90Q29uZmlnKQ0KICApICsNCiAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCwgdmFyd2lkdGggPSBULCBhbHBoYSA9IDApICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkNCg0KZ2dwbG90KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICBtYXBwaW5nID0gYWVzKA0KICAgIHggPSBOZWlnaGJvcmhvb2QsDQogICAgeSA9IGBsb2coU2FsZVByaWNlKWAsDQogICkNCikgKw0KICBnZW9tX2ppdHRlcigNCiAgICBhbHBoYSA9IC4zLA0KICAgIG1hcHBpbmcgPSBhZXMoY29sb3IgPSBMb3RTaGFwZSwgc2hhcGUgPSBMb3RTaGFwZSkNCiAgKSArDQogIGdlb21fYm94cGxvdChub3RjaCA9IFQsIHZhcndpZHRoID0gVCwgYWxwaGEgPSAwKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQoNCmdncGxvdCgNCiAgZGF0YSA9IHZhbF90cmFpbl9YeSwNCiAgbWFwcGluZyA9IGFlcygNCiAgICB4ID0gTG90Q29uZmlnLA0KICAgIHkgPSBgbG9nKFNhbGVQcmljZSlgLA0KICApDQopICsNCiAgZ2VvbV9qaXR0ZXIoDQogICAgYWxwaGEgPSAuNCwNCiAgICBtYXBwaW5nID0gYWVzKGNvbG9yID0gTG90U2hhcGUsIHNoYXBlID0gTG90U2hhcGUpDQogICkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBULCB2YXJ3aWR0aCA9IFQsIGFscGhhID0gMCkNCmBgYA0KDQo8YSBpZD0ibG90c2hhcGVMb3Rjb25maWciPjwvYT4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dwbG90KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICBtYXBwaW5nID0gYWVzKA0KICAgIHggPSBMb3RTaGFwZSwNCiAgICB5ID0gYGxvZyhTYWxlUHJpY2UpYCwNCiAgKQ0KKSArDQogIGdlb21faml0dGVyKA0KICAgIGFscGhhID0gLjQsDQogICAgbWFwcGluZyA9IGFlcyhjb2xvciA9IExvdENvbmZpZywgc2hhcGUgPSBMb3RDb25maWcpDQogICkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBULCB2YXJ3aWR0aCA9IFQsIGFscGhhID0gMCkNCmBgYA0KDQpUaGVyZSBhcHBlYXJzIHRvIGJlIHNvbWUgb3ZlcmxhcCB3aXRoIGxvdCBzaGFwZSBhbmQgbG90IGNvbmZpZ3VyYXRpb24uDQoNCiMjIExhbmRTbG9wZQ0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpWYXN0IG1ham9yaXR5IGFyZSBnZW50bGUgZHJvcCBpdC4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc3VtbWFyeSh2YWxfdHJhaW5fWHkkTGFuZFNsb3BlKQ0KdmFsX3RyYWluX1h5ID0gc2VsZWN0KHZhbF90cmFpbl9YeSwgLWMoJ0xhbmRTbG9wZScpKQ0KYGBgDQoNCiMjIE5laWdoYm9yaG9vZA0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ05laWdoYm9yaG9vZCcNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KeSA9ICdsb2coU2FsZVByaWNlKScNCnN1bV9hbmRfdHJhbnNfZmFjdChkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB6ID0geSkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXlzaXplID0gMg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCmBgYA0KDQpOb3J0aCBBbWVzIChOYW1lcykgYW5kIENvbGxnQ3IgaGF2ZSB0aGUgbW9zdCByZXNpZGVudGlhbCBob21lcyAoMTA3IGFuZCA3NikuIGJ1dCB0aGV5J3JlIGFsc28gem9uZWQgbG93LWRlbnNpdHkuIEEgaGFuZGZ1bCBvZiBuZWlnaGJvcmhvb2RzIGhhdmUgYWxtb3N0IG5vIGhvdXNlcyBpbiB0aGlzIHNldC4NCg0KVGhlIHByaWNpZXN0IG5laWdoYm9yaG9vZHMgYXJlIFN0b25lQnIsIE5yaWRnZUh0LCBhbmQgTm9SaWRnZS4gVGhlIGxlYXN0IHByaWNleSAoT2xkVG93biwgTWVhZG93ViwgYW5kIElET1RSUikgYXJlIGFsc28gdGhlIG1vc3QgZGVuc2UgYW5kIGNvbW1lcmNpYWwuIFRoZXJlIGFyZSBzaWduaWZpY2FudCBkaWZmZXJlbmNlcyBpbiBwcmljZXMgYmV0d2VlbiBtYW55IG5laWdoYm9yaG9vZHMsIGFuZCBub3QganVzdCBiZXR3ZWVuIHRoZSBjaGVhcGVzdCBhbmQgcHJpY2llc3QuDQoNCjxhIGlkPSJuZWlnaGJab25pbmciPjwvYT4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dwbG90KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICBhZXMoeCA9IE5laWdoYm9yaG9vZCwgZmlsbCA9IE1TWm9uaW5nKQ0KKSArDQogIGdlb21fYmFyKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KYGBgDQoNCiMjIENvbmRpdGlvbjEsIENvbmRpdGlvbjINCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KVGhlIHZhc3QgbWFqb3JpdHkgYXJlIG5vcm1hbC4gRHJvcCBDb25kaXRpb24yLiBCdXQsIGZvciBDb25kaXRpb24xLCB0aGVyZSBhcHBlYXIgdG8gYmUgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZXMgaW4gU2FsZVByaWNlIGJldHdlZW4gTm9ybSBhbmQgRmVlZHIsIGFuZCBtYXliZSBiZXR3ZWVuIE5vcm0gYW5kIEFydGVyeSBidXQgdGhlcmUgYXJlIHRvbyBmZXcgQXJ0ZXJ5IG9ic2VydmF0aW9ucy4gSXQgbWlnaHQgYmUgd29ydGggb25lLWhvdC1lbmNvZGluZyB0aG9zZSBjYXRlZ29yaWVzLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ0NvbmRpdGlvbjEnDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5KQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQpgYGANCg0KWW91IG1pZ2h0IGNvbnNpZGVyIGJpbmFyaXppbmcsIGx1bXBpbmcgJ0ZlZWRyJyBhbmQgJ0FydGVyeScgYW5kIG1heWJlICdSUkFlJyB0b2dldGhlciBhbmQgbHVtcGluZyB0aGUgcmVzdCB3aXRoICdOb3JtJy4gSSdsbCB0cnkgaXQgb3V0IGR1cmluZyBmZWF0dXJlIHNlbGVjdGlvbiB3aXRoIGNhcmV0IGluIHRoZSBNTCBwaGFzZS4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc3VtbWFyeSh2YWxfdHJhaW5fWHkkQ29uZGl0aW9uMikNCnZhbF90cmFpbl9YeSA9IHNlbGVjdCh2YWxfdHJhaW5fWHksIC1jKCdDb25kaXRpb24yJykpDQpgYGANCg0KIyMgQmxkZ1R5cGUNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdCbGRnVHlwZScNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KDQpwX3ZhbHMgPSBnZXRfc2lnbmlmX2xldmVscyhkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeiA9IHkpDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCmBgYA0KDQpNYWpvcml0eSBzaW5nbGUtZmFtaWx5ICg2MDMpLCAyNCBOQXMuIEl0IHNlZW1zIGxpa2UgYW4gaW5oZXJlbnRseSBpbXBvcnRhbnQgZmVhdHVyZSwgZGVzcGl0ZSB0aGUgbG9wc2lkZWRuZXNzIG9mIHRoZSBkaXN0cmlidXRpb24uIFByb2JhYmx5IHNhZmUgdG8gaW1wdXRlIHRvIG1vZGUgKDFGYW1pbHkpLCBidXQgb3RoZXIgZmVhdHVyZXMgbWlnaHQgaW5mb3JtLCBzdWNoIGFzIE1TU3ViQ2xhc3MsIE1TWm9uaW5nLCBOZWlnaGJvcmhvb2QsIEhvdXNlU3R5bGUsIGFuZCBidWlsZGluZyBtYXRlcmlhbHM7IG11bHRpdmFyaWF0ZSBpbXB1dGUgbWlnaHQgYmUgaW4gb3JkZXIsIGlmIGtlZXBpbmcgdGhlIGZlYXR1cmUgaW4gdGhlIGZpcnN0IHBsYWNlLg0KDQpEdXBsZXhlcyBhbmQgdHdvLWZhbWlseSBhcmUgc2lnbmlmaWNhbnRseSBjaGVhcGVyIHRoYW4gc2luZ2xlLWZhbWlseSBhbmQgdG93bmhvdXNlcywgaWYgYWNjZXB0aW5nIHRoZSBsb3cgbnVtYmVyIGluIHRoZSBzYW1wbGUuIENhbmRpZGF0ZSBmb3IgYmluYXJpemF0aW9uLCBidXQgbWF5IGxvc2UgaW50ZXJhY3Rpb25zLg0KDQojIyBIb3VzZVN0eWxlDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnSG91c2VTdHlsZScNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KDQpwX3ZhbHMgPSBnZXRfc2lnbmlmX2xldmVscyhkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeiA9IHksIG1pbl9uID0gMjkpDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCmBgYA0KDQpNb3N0bHkgb25lLXN0b3J5ICgzNTgpLCBidXQgbWFueSB0d28tc3RvcnkgKDIyMCkuIFNldmVyYWwgc2lnbmlmaWNhbnQgcHJpY2UgZGlmZmVyZW5jZXMgYWNyb3NzIGdyb3Vwcy4gTWF5YmUgd29ydGgga2VlcGluZywgYnV0IGFsc28ga2luZCBvZiBub2lzeSB3aXRoIHRoZSBmaW5pc2hlZC91bmZpbmlzaGVkIGJ1c2luZXNzIG9ubHkgYXBwbHlpbmcgdG8gaGFsZi1zdG9yaWVzLCBhbmQgbnVtYmVyIG9mIHN0b3JpZXMgYW5kIGZpbmlzaGVkIHN0YXR1cyBhcmUgZW5jb2RlZCBpbiBvdGhlciBmZWF0dXJlcy4NCg0KIyMgT3ZlcmFsbFF1YWwNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdPdmVyYWxsUXVhbCcNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KDQpwX3ZhbHMgPSBnZXRfc2lnbmlmX2xldmVscyhkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeiA9IHksIG1pbl9uID0gMjkpDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCmBgYA0KDQpUaGVyZSdzIGEgdmVyeSBzdHJvbmcgcmVsYXRpb25zaGlwIHRvIFNhbGVQcmljZS4NCg0KIyMjIE5vcm1hbGl6ZQ0KDQpJdCdzIGEgcHJldHR5IG5vcm1hbCBkaXN0cmlidXRpb24sIHNsaWdodGx5IGxlZnQtc2tld2VkLiBNb2RlICgyMTk5IDVzKSBsZWZ0IG9mIG1lZGlhbi9tZWFuICgxODAgNnMpLCBmZXcgMXMsIDJzLCBhbmQgM3MuIEl0IG1pZ2h0IGJlIHdvcnRoIGNhc3RpbmcgYXMgYW4gaW50ZWdlciBhbmQgdHJhbnNmb3JtaW5nIHRvIG5vcm1hbGl6ZSBhbmQgcG9zc2libHkgaW1wcm92ZSB0aGUgY29ycmVsYXRpb24uDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnT3ZlcmFsbFF1YWxfaW50Jw0KDQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZShPdmVyYWxsUXVhbF9pbnQgPSBhcy5pbnRlZ2VyKE92ZXJhbGxRdWFsKSkNCg0KIyBSZWNhbGN1bGF0ZSBiZXN0IG5vcm1hbGl6ZXJzLg0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KYmVzdF9ub3JtYWxpemVycyA9IGZpbmRfYmVzdF9ub3JtYWxpemVyX3Blcl9mZWF0KA0KICBkZiA9IHZhbF90cmFpbl9YeSwNCiAgZmVhdHNfbHN0ID0gbnVtX2ZlYXRzLA0KICBmdW5jc19sc3QgPSBmdW5jc19sc3QsDQogIGV4Y2x1ZGVfdmFscyA9IGxpc3QoMCkNCikNCg0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbeF0pDQpzdW1fYW5kX3RyYW5zX2NvbnQoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHksDQogIHggPSB4LA0KICBmdW5jID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRmdW5jLA0KICBmdW5jX25hbWUgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJG5hbWUsDQogIHhfYmludyA9IDEsDQogIHRfYmludyA9IDENCikNCmBgYA0KDQpOb25lIG9mIHRoZSB0cmFuc2Zvcm1hdGlvbnMgaW1wcm92ZWQgaXRzIGRpc3RyaWJ1dGlvbi4NCg0KIyMjIENvcnJlbGF0aW9ucw0KDQpIZXJlIGFyZSB0aGUgY29ycmVsYXRpb25zIGJldHdlZW4gT3ZlcmFsbFF1YWxfaW50IGFuZCB0aGUgcmVzdCBvZiB0aGUgdmFyaWFibGVzLg0KDQpUaGlzIGZlYXR1cmUgaGFzIHNldmVyYWwgbW9kZXJhdGUgY29ycmVsYXRpb25zIHRvIGZlYXR1cmVzIGhhdmluZyB0byBkbyB3aXRoIHNpemUgYW5kIGFnZS4gSXQgYWxzbyBoYXMgYSBzdHJvbmcgY29ycmVsYXRpb24gdG8gdHJhbnNmb3JtZWQgU2FsZVByaWNlICgwLjgyKS4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeF9sc3QgPSBjKHgpDQoNCmRmID0gZ2V0X2NvcnMoDQogIGRhdGEgPSBmaWx0ZXIoDQogICAgc2VsZWN0KHZhbF90cmFpbl9YeSwgYWxsX29mKG51bV9mZWF0cykpLA0KICAgICFpcy5uYSguZGF0YVtbeF1dKQ0KICApLA0KICB4X2xzdCA9IHhfbHN0LA0KICBmZWF0cyA9IG51bV9mZWF0cw0KKQ0KZGYNCnByaW50KCJTdW1tYXJ5IG9mIGFic29sdXRlIHZhbHVlcyBvZiBQZWFyc29uJ3MgUnM6IikNCmRmID0gYWJzKGRmKQ0Kc3VtbWFyeShhYnMoZGYpKQ0KDQpkZiA9IG1lbHQoZGYpDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gdmFyaWFibGUsIHkgPSB2YWx1ZSkpICsNCiAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKw0KICB5bGFiKGxhYmVsID0gJ0Fic29sdXRlIFZhbHVlIG9mIENvcnJlbGF0aW9uIHRvIE90aGVyIEZlYXR1cmVzJykNCmBgYA0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp5X2xzdCA9IGMoJ2xvZyhTYWxlUHJpY2UpJywgJ1llYXJCdWlsdCcsICdZZWFyUmVtb2RBZGQnLCAnVG90YWxCc210U0YnLA0KICAgICAgICAgICdHckxpdkFyZWEnLCdGdWxsQmF0aCcsICdUb3RSbXNBYnZHcmQnLCAnR2FyYWdlWXJCbHQnLCAnR2FyYWdlQ2FycycsDQogICAgICAgICAgJ0dhcmFnZUFyZWEnKQ0KcGxvdF9zY2F0X3BhaXJzKGRmID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeV9sc3QgPSB5X2xzdCkNCmBgYA0KDQojIyMgQnkgRmFjdG9ycw0KDQpBZ2FpbiwgdGhlcmUncyBhIGxvdCBvZiBpbnRlcmFjdGlvbiB3aXRoIE1TU3ViQ2xhc3MgYW5kIE5laWdoYm9yaG9vZC4gUXVhbGl0eSBhbHNvIGRlY3JlYXNlcyB3aXRoIHpvbmluZyBkZW5zaXR5LiBUd28tc3RvcnkgaG91c2VzIGFyZSBnZW5lcmFsbHkgcmF0ZWQgaGlnaGVyIHRoYW4gb25lLXN0b3J5IGhvdXNlcy4gVmlueWwgZ2V0cyByYXRlZCBoaWdoZXN0IGFtb25nIGV4dGVyaW9ycy4gU2ltcGx5IGhhdmluZyBtYXNvbnJ5IGltcHJvdmVzIHF1YWxpdHkgcmF0aW5nLiBQb3VyZWQgY29uY3JldGUgZm91bmRhdGlvbnMgb24gYXZlcmFnZSByZWNlaXZlIDdzLCB3aGVyZWFzIGNpbmRlciBibG9jayBhbmQgYnJpY2svdGlsZSByZWNlaXZlIDVzIG9uIGF2ZXJhZ2UuIFRoZSBzYW1lIGlzIHRydWUgZm9yIGF0dGFjaGVkIGFuZCBidWlsdC1pbiBnYXJhZ2VzIGNvbXBhcmVkIHRvIGRldGFjaGVkIGFuZCBubyBnYXJhZ2VzLiBJdCBhbG1vc3QgbG9va3MgbGlrZSBpdCdzIGJldHRlciB0byBoYXZlIG5vIGZlbmNlIHRoYW4gdG8gaGF2ZSBvbmUgd2l0aCBtaW5vciBwcml2YWN5LiBDb3VydCBvZmZpY2VyIGRlZWRzL2VzdGF0ZXMgYXJlIHJhdGVkIG1vcmUgcG9vcmx5IHRoYW4gd2FycmFudHkgZGVlZHMgYW5kIG5ldyBzYWxlcy4gQWJub3JtYWwgc2FsZXMgYXJlIHJhdGVkIGxvd2VyIHRoYW4gbm9ybWFsIHNhbGVzLg0KDQpPdmVyYWxsIHF1YWxpdHkgaXMgZG9pbmcgYSBsb3Qgb2YgdGhlIHdvcmsgZm9yIG90aGVyIGZlYXR1cmVzIHRvd2FyZCBwcmVkaWN0aW5nIHByaWNlLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp5X2xzdCA9IGMoJ01TU3ViQ2xhc3MnLCAnTVNab25pbmcnLCAnTmVpZ2hib3Job29kJywgJ0JsZGdUeXBlJywgJ0hvdXNlU3R5bGUnLA0KICAgICAgICAgICdSb29mTWF0bCcsICdSb29mU3R5bGUnLCAnRXh0ZXJpb3Ixc3QnLCAnTWFzVm5yVHlwZScsICdGb3VuZGF0aW9uJywNCiAgICAgICAgICAnSGVhdGluZycsICdFbGVjdHJpY2FsJywgJ0Z1bmN0aW9uYWwnLCAnR2FyYWdlVHlwZScsICdHYXJhZ2VGaW5pc2gnLA0KICAgICAgICAgICdGZW5jZScsICdNaXNjRmVhdHVyZScsICdTYWxlVHlwZScsICdTYWxlQ29uZGl0aW9uJykNCmZvciAoeSBpbiB5X2xzdCkgew0KICBwbHQgPSBmZW5jZWRfamJ2KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB5LCB5ID0geCkgKw0KICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0PTEpKQ0KICBwcmludChwbHQpDQp9DQpgYGANCg0KIyMgT3ZlcmFsbENvbmQNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdPdmVyYWxsQ29uZCcNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KDQpwX3ZhbHMgPSBnZXRfc2lnbmlmX2xldmVscyhkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeiA9IHksIG1pbl9uID0gMjkpDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCmBgYA0KDQpPdmVyYWxsQ29uZCBpcyBsaWtlIE92ZXJhbGxRdWFsLCBidXQgd2l0aCBhIG11Y2ggbW9yZSBwcm9ub3VuY2VkIG1vZGUgKDM5NyA1cykgbGVmdCBvZiBtZWRpYW4vbWVhbiAoMTI0IDZzKSwgcHJvYmFibHkgZHVlIHRvIHdlYXIgYW5kIHRlYXIgYmVpbmcgbW9yZSB1bml2ZXJzYWwgdGhhbiBxdWFsaXR5IGNvbnN0cnVjdGlvbi4NCg0KVGhlIGNvcnJlbGF0aW9uIHRvIFNhbGVQcmljZSBpcyB3ZWFrZXIsIGFuZCA1cyBvZGRseSBzZWVtIHRvIHNlbGwgZm9yIG1vcmUgb24gYXZlcmFnZSB0aGFuIGhpZ2hlci1yYXRlZCBob3VzZXMuDQoNCiMjIyBOb3JtYWxpemUNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdPdmVyYWxsQ29uZF9pbnQnDQoNCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKE92ZXJhbGxDb25kX2ludCA9IGFzLmludGVnZXIoT3ZlcmFsbENvbmQpKQ0KDQojIFJlY2FsY3VsYXRlIGJlc3Qgbm9ybWFsaXplcnMuIE1pZ2h0IGFzIHdlbGwgZG8gdGhlbSBhbGwsIHNlZSBpZiBwcmV2aW91cw0KIyB0cmFuc2Zvcm1hdGlvbnMgYmVuZWZpdCBmcm9tIGZ1cnRoZXIgdHJhbnNmb3JtYXRpb24sIHdoaWxlIHdlJ3JlIGF0IGl0Lg0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KYmVzdF9ub3JtYWxpemVycyA9IGZpbmRfYmVzdF9ub3JtYWxpemVyX3Blcl9mZWF0KA0KICBkZiA9IHZhbF90cmFpbl9YeSwNCiAgZmVhdHNfbHN0ID0gbnVtX2ZlYXRzLA0KICBmdW5jc19sc3QgPSBmdW5jc19sc3QsDQogIGV4Y2x1ZGVfdmFscyA9IGxpc3QoMCkNCikNCg0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbeF0pDQpzdW1fYW5kX3RyYW5zX2NvbnQoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHksDQogIHggPSB4LA0KICBmdW5jID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRmdW5jLA0KICBmdW5jX25hbWUgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJG5hbWUsDQogIHhfYmludyA9IDEsDQogIHRfYmludyA9IDENCikNCmBgYA0KDQoNCiMjIyBDb3JyZWxhdGlvbnMNCg0KVGhpcyBmZWF0dXJlIGlzIG9ubHkgd2Vha2x5IGNvcnJlbGF0ZWQgdG8gYSBjb3VwbGUgb2YgYWdlIGZlYXR1cmVzLiBJdCBoYXMgbm8gbGluZWFyIGNvcnJlbGF0aW9uIHRvIFNhbGVQcmljZS4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeF9sc3QgPSBjKHgpDQoNCmRmID0gZ2V0X2NvcnMoDQogIGRhdGEgPSBmaWx0ZXIoDQogICAgc2VsZWN0KHZhbF90cmFpbl9YeSwgYWxsX29mKG51bV9mZWF0cykpLA0KICAgICFpcy5uYSguZGF0YVtbeF1dKQ0KICApLA0KICB4X2xzdCA9IHhfbHN0LA0KICBmZWF0cyA9IG51bV9mZWF0cw0KKQ0KZGYNCnByaW50KCJTdW1tYXJ5IG9mIGFic29sdXRlIHZhbHVlcyBvZiBQZWFyc29uJ3MgUnM6IikNCmRmID0gYWJzKGRmKQ0Kc3VtbWFyeShhYnMoZGYpKQ0KDQpkZiA9IG1lbHQoZGYpDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gdmFyaWFibGUsIHkgPSB2YWx1ZSkpICsNCiAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKw0KICB5bGFiKGxhYmVsID0gJ0Fic29sdXRlIFZhbHVlIG9mIENvcnJlbGF0aW9uIHRvIE90aGVyIEZlYXR1cmVzJykNCmBgYA0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp5X2xzdCA9IGMoJ2xvZyhTYWxlUHJpY2UpJywgJ1llYXJCdWlsdCcsICdZZWFyUmVtb2RBZGQnLCAnR2FyYWdlWXJCbHQnKQ0KcGxvdF9zY2F0X3BhaXJzKGRmID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeV9sc3QgPSB5X2xzdCkNCmBgYA0KDQpUaGUgYnVtcCBpbiBwcmljZSBhbW9uZyBob3VzZXMgb2YgYXZlcmFnZSBjb25kaXRpb24gc2VlbXMgdG8gaGF2ZSB0byBkbyB3aXRoIGEgY2x1c3RlciBvZiBob3VzZXMgbWFkZSBpbiB0aGUgbGF0ZSAnOTBzIGFuZCBlYXJseSAyMDAwcy4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dwbG90KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICBtYXBwaW5nID0gYWVzKA0KICAgIHggPSBPdmVyYWxsQ29uZF9pbnQsDQogICAgeSA9IC5kYXRhW1snbG9nKFNhbGVQcmljZSknXV0sDQogICAgY29sb3IgPSBZZWFyQnVpbHQNCiAgKQ0KKSArDQogIGdlb21faml0dGVyKGFscGhhID0gMC41KSArDQogIGdlb21fc21vb3RoKCkgIysNCiAgIyBmYWNldF93cmFwKHZhcnMoQmxkZ1R5cGUpKQ0KYGBgDQoNCiMjIyBCeSBGYWN0b3JzDQoNCk9sZFRvd24gc2VlbXMgdG8gYmUgaW4gcmVsYXRpdmVseSBnb29kIHNoYXBlLCB3aGlsZSBFZHdhcmRzIGhhcyBwcm9wb3J0aW9uYXRlbHkgbW9yZSBob3VzZXMgaW4gbmVlZCBvZiB3b3JrIGFuZCByZWNvbmRpdGlvbmluZy4gWW91IGNhbiBlYXNpbHkgc3BvdCBjbHVzdGVyZWQgNXMgaW4gdGhlIG5laWdoYm9yaG9vZHMgdGhhdCBsaWtlbHkgZ3JldyB1cCBpbiB0aGUgYnVpbGRpbmcgYm9vbSBvZiB0aGUgMjAwMHMuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnlfbHN0ID0gYygnTVNTdWJDbGFzcycsICdNU1pvbmluZycsICdOZWlnaGJvcmhvb2QnLCAnQ29uZGl0aW9uMScsICdCbGRnVHlwZScsDQogICAgICAgICAgJ0hvdXNlU3R5bGUnLCAnUm9vZlN0eWxlJywgJ1Jvb2ZNYXRsJywgJ0V4dGVyaW9yMXN0JywgJ01hc1ZuclR5cGUnLA0KICAgICAgICAgICdCc210RXhwb3N1cmUnLCAnRm91bmRhdGlvbicsICdIZWF0aW5nJywgJ0VsZWN0cmljYWwnLCAnRnVuY3Rpb25hbCcsDQogICAgICAgICAgJ0dhcmFnZVR5cGUnLCAnR2FyYWdlRmluaXNoJywgJ0ZlbmNlJywgJ01pc2NGZWF0dXJlJywgJ1NhbGVUeXBlJywNCiAgICAgICAgICAnU2FsZUNvbmRpdGlvbicpDQpmb3IgKHkgaW4geV9sc3QpIHsNCiAgcGx0ID0gZmVuY2VkX2pidihkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geSwgeSA9IHgpICsNCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdD0xKSkNCiAgcHJpbnQocGx0KQ0KfQ0KYGBgDQoNCkhvdXNlcyB3aXRoIG1ldGFsIGFuZCB3b29kIGV4dGVyaW9ycyBhcmUgdHlwaWNhbGx5IGluIGJldHRlciBjb25kaXRpb24gdGhhbiB0aG9zZSB3aXRoIHZpbnlsIGRlc3BpdGUgaG91c2VzIHdpdGggdmlueWwgc2lkaW5nIHR5cGljYWxseSBiZWluZyByYXRlZCBhcyBoaWdoZXIgcXVhbGl0eSBhbmQgbmV3ZXIuIExpa2V3aXNlLCBob3VzZXMgd2l0aCBjaW5kZXIgYmxvY2sgZm91bmRhdGlvbnMgc2VlbSB0byBmYXJlIGJldHRlciBvdmVyIHRpbWUgdGhhbiB0aG9zZSB3aXRoIHBvdXJlZCBjb25jcmV0ZSBkZXNwaXRlIHF1YWxpdHkgcmF0aW5ncywgYWNjb3JkaW5nIHRvIHRoaXMgZGF0YSBzZXQsIGFzIGRvIGhvdXNlcyB3aXRoIGRldGFjaGVkIGdhcmFnZXMgY29tcGFyZWQgdG8gdGhvc2Ugd2l0aCBhdHRhY2hlZCBhbmQgYnVpbHQtaW4gZ2FyYWdlcy4gSXMgdGhpcyBiZWNhdXNlIG9sZGVyIGhvdXNlcyBhbmQgbG93ZXItcXVhbGl0eSBob3VzZXMgdGhhdCBhcmUgc3RpbGwgc3RhbmRpbmcgYXJlIHRoZSBvbmVzIHRoYXQgaGF2ZSByZWNlaXZlZCBiZXR0ZXIgbWFpbnRlbmFuY2U/DQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnAxID0gZ2dwbG90KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICBtYXBwaW5nID0gYWVzKA0KICAgIHkgPSBPdmVyYWxsQ29uZF9pbnQsDQogICAgeCA9IEV4dGVyaW9yMXN0LA0KICAgIGNvbG9yID0gWWVhckJ1aWx0DQogICkNCikgKw0KICBnZW9tX2ppdHRlcigpICsNCiAgZ2VvbV9ib3hwbG90KGFscGhhID0gMCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdD0xKSkNCg0KcDIgPSBnZ3Bsb3QoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHksDQogIG1hcHBpbmcgPSBhZXMoDQogICAgeSA9IE92ZXJhbGxDb25kX2ludCwNCiAgICB4ID0gRm91bmRhdGlvbiwNCiAgICBjb2xvciA9IFllYXJCdWlsdA0KICApDQopICsNCiAgZ2VvbV9qaXR0ZXIoKSArDQogIGdlb21fYm94cGxvdChhbHBoYSA9IDApICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3Q9MSkpDQoNCnAzID0gZ2dwbG90KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICBtYXBwaW5nID0gYWVzKA0KICAgIHkgPSBPdmVyYWxsQ29uZF9pbnQsDQogICAgeCA9IEdhcmFnZVR5cGUsDQogICAgY29sb3IgPSBZZWFyQnVpbHQNCiAgKQ0KKSArDQogIGdlb21faml0dGVyKCkgKw0KICBnZW9tX2JveHBsb3QoYWxwaGEgPSAwKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0PTEpKQ0KDQpncmlkLmFycmFuZ2UocDEsIHAyLCBwMywgbmNvbCA9IDIpDQpgYGANCg0KIyMgWWVhckJ1aWx0DQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNCk5vIHRyYW5zZm9ybWF0aW9ucyBub3JtYWxpemVkIHRoZSBkaXN0cmlidXRpb24uIFlvdSBjYW4gc2VlIHRoZSBjb25zdHJ1Y3Rpb24gYm9vbSBiZXR3ZWVuIFdXSUkgYW5kIHRoZSAnODBzICh3aXRoIGEgZGlwIGluIG1pZCAnNzBzIHN0YWdmbGF0aW9uKSwgZm9sbG93ZWQgYnkgdGhlIHJlbGF0aXZlIGV4cGxvc2lvbiBzdGFydGluZyBpbiB0aGUgbGF0ZSAnOTBzIGFuZCBkcm9wcGluZyB3aXRoIHRoZSBob3VzaW5nIGNyaXNpcyBpbiB0aGUgMjAwMHMuDQoNCkNvbnNpZGVyIGdyb3VwaW5nIGFyb3VuZCBtb2RlcyBpbnRvIGEgZmFjdG9yIGFuZCBkdW1teSBjb2RpbmcgdG8gYWNjb21tb2RhdGUgc29tZSBNTCBhbGdvcml0aG1zIHRoYXQgd291bGRuJ3QgaGFuZGxlIGEgcG9seW1vZGFsIGRpc3RyaWJ1dGlvbiB3ZWxsLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ1llYXJCdWlsdCcNCnN1bW1hcnkodmFsX3RyYWluX1h5W3hdKQ0KDQojIHN1bV9hbmRfdHJhbnNfY29udCgNCiMgICBkYXRhID0gdmFsX3RyYWluX1h5LA0KIyAgIHggPSB4LA0KIyAgIGZ1bmMgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJGZ1bmMsDQojICAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KIyAgIHhfYmludyA9IDEsDQojICAgdF9iaW53ID0gMQ0KIyApDQoNCmdncGxvdCh2YWxfdHJhaW5fWHksIGFlcyh4ID0gWWVhckJ1aWx0KSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEpDQpgYGANCg0KIyMjIENvcnJlbGF0aW9ucw0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ1llYXJCdWlsdCcNCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCnhfbHN0ID0gYyh4KQ0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAhaXMubmEoLmRhdGFbW3hdXSkNCiAgKSwNCiAgeF9sc3QgPSB4X2xzdCwNCiAgZmVhdHMgPSBudW1fZmVhdHMNCikNCmRmDQpwcmludCgiU3VtbWFyeSBvZiBhYnNvbHV0ZSB2YWx1ZXMgb2YgUGVhcnNvbidzIFJzOiIpDQpkZiA9IGFicyhkZikNCnN1bW1hcnkoYWJzKGRmKSkNCg0KZGYgPSBtZWx0KGRmKQ0KZ2dwbG90KGRmLCBhZXMoeCA9IHZhcmlhYmxlLCB5ID0gdmFsdWUpKSArDQogIGdlb21fYm94cGxvdChub3RjaCA9IFQpICsNCiAgeWxhYihsYWJlbCA9ICdBYnNvbHV0ZSBWYWx1ZSBvZiBDb3JyZWxhdGlvbiB0byBPdGhlciBGZWF0dXJlcycpDQpgYGANCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeV9sc3QgPSBjb2xuYW1lcyhzZWxlY3QodmFsX3RyYWluX1h5LCB3aGVyZShpcy5udW1lcmljKSkpDQpmb3IgKHkgaW4geV9sc3QpIHsNCiAgZm9yICh4IGluIHhfbHN0KSB7DQogICAgcGx0ID0gZ2dwbG90KA0KICAgICAgc2VsZWN0KHZhbF90cmFpbl9YeSwgYWxsX29mKGMoeCwgeSkpKSwNCiAgICAgIGFlcyh4ID0gLmRhdGFbW3hdXSwgeSA9IC5kYXRhW1t5XV0pDQogICAgKSArDQogICAgICBnZW9tX2ppdHRlcigpICsNCiAgICAgIGdlb21fc21vb3RoKCkgKw0KICAgICAgbGFicyh4ID0geCwgeSA9IHkpICsNCiAgICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMTg4MCwgMjAxMCwgNSkpICsNCiAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpDQogICAgcHJpbnQocGx0KQ0KICB9DQp9DQpgYGANCg0KIyMjIEJ5IEZhY3RvcnMNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeV9sc3QgPSBjb2xuYW1lcyhzZWxlY3QodmFsX3RyYWluX1h5LCB3aGVyZShpcy5mYWN0b3IpKSkNCmZvciAoeSBpbiB5X2xzdCkgew0KICBwbHQgPSBmZW5jZWRfamJ2KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB5LCB5ID0geCkgKw0KICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQogIHByaW50KHBsdCkNCn0NCmBgYA0KDQojIyMgQSBTdG9yeSBvZiBBbWVzDQoNCjxhIGlkPSJBbWVzU3RvcnkiPjwvYT4NCg0KQmVmb3JlIGxvb2tpbmcgYXQgcmVtb2RlbCB5ZWFyLCB0aGlzIGlzIHRoZSBzdG9yeSBJIGdhdGhlciBhYm91dCBob3VzZXMgcHVyY2hhc2VkIGluIHRoaXMgcGVyaW9kIGJhc2VkIG9uIHRoZSBhYm92ZSB2aXN1YWxpemF0aW9ucyBvZiBZZWFyQnVpbHQuIEkgY291bGQgdHJ5IHRvIHdlYXZlIHRoZSB2aXogaW4gd2l0aCB0aGUgbmFycmF0aXZlLCBidXQgSSdtIG5vdCBnb2luZyB0by4NCg0KSG91c2VzIGFuZCBnYXJhZ2VzIGhhdmUgZ290dGVuIGJpZ2dlciBvdmVyIHRoZSB5ZWFycywgd2l0aCBtb3JlIGJhdGhyb29tcyBhbmQgbGVzcyBlbmNsb3NlZCBwb3JjaCBzcGFjZS4gKEknbSBjdXJpb3VzIHRvIHNlZSB3aGV0aGVyIHRoZSBpbmNyZWFzaW5nIHJhcml0eSBvZiBlbmNsb3NlZCBwb3JjaCBzcGFjZSBsZWQgdG8gaXQgYmVpbmcgYSBwcml6ZWQgKHByaWNlZCkgZmVhdHVyZSwgb3IgaWYgaXQgc2ltcGx5IGlzbid0IGFzIHZhbHVlZCBub3cuKSBOZXdlciBob21lcyB0ZW5kIHRvIGJlIHZhbHVlZCBtb3JlLCBhbmQgYnVpbGRlcnMgZ2VuZXJhbGx5IGJlZ2FuIHRvIHByb2R1Y2UgaGlnaGVyLXF1YWxpdHkgaG91c2VzLg0KDQpNb3N0IG9mIHRoZSBob3VzZXMgYnVpbHQgaW4gdGhlIHBvc3QtV1dJSSBib29tIHdlcmUgc2luZ2xlLXN0b3J5LCBidXQgdGhhdCdzIGFsc28gd2hlbiAyIDEvMiBzdG9yaWVzIGJlY2FtZSB1bmhlYXJkLW9mLiBTcGxpdC9tdWx0aS1sZXZlbCBhbmQgZHVwbGV4ZXMgYmVnYW4gdG8gY29tZSBvbiB0aGUgc2NlbmUuIDEgMS8yIHN0b3JpZXMgaGFkIHRoZWlyIGhleWRheS4gU29tZSBvZiB0aGUgaG91c2VzIGZyb20gdGhlbiBhbmQgZWFybGllciBtYWtlIHVwIHRoZSBncm91cCBvZiB0d28tZmFtaWx5IGNvbnZlcnNpb25zLiBJdCB3b3VsZCBiZSBpbnRlcmVzdGluZyB0byBjaGVjayByZW1vZGVsIHllYXJzIHRvIHNlZSB3aGVuIHRob3NlIGNvbnZlcnNpb25zIHRlbmRlZCB0byB0YWtlIHBsYWNlLg0KDQpBcyB0aGUgY2VudHVyeSB0dXJuZWQsIHByaWNlcyBncmV3IGV4cG9uZW50aWFsbHkuIFR3by1zdG9yeSBob3VzZXMgYW5kIFBVRCBob3VzaW5nIGJlY2FtZSBtb3JlIHByZXZhbGVudCwgYW5kIHRvd25ob3VzZXMgYXBwZWFyZWQgZm9yIHRoZSBmaXJzdCB0aW1lLiBab25pbmcgYmVjYW1lIGxlc3MgZGVuc2UgYXMgbmV3IHN1YnVyYnMgc3ByYW5nIHVwLiBMb3RzIGJlY2FtZSBtb3JlIGlycmVndWxhci4gQ3VsIGRlIHNhY3MgYW5kIHBhcmtzIHN0YXJ0ZWQgdG8gc3ByaW5rbGUgaW4gYXMgZmVlZGVyIHN0cmVldHMgbmV0d29ya2VkIG91dC4gQSBoYW5kZnVsIG9mIGhvdXNlcyBiZWdhbiB0byBhcHBlYXIgbmVhciByYWlscm9hZHMuDQoNCkV2ZW4gYXMgbWFzb25yeSBiZWNhbWUgYSBncm93aW5nIGJsaW5nIGZhY3RvciBkcml2aW5nIHVwIG92ZXJhbGwgcXVhbGl0eSwgdGhlIGFnZSBvZiBwbGFzdGljIHVzaGVyZWQgaW4gdmlueWwgc2lkaW5nIGFzIHRoZSBkb21pbmFudCBleHRlcmlvci4gVGhlIG92ZXJhbGwgY29uZGl0aW9uIG9mIGhvdXNlcyBmcm9tIHRoZSB0dXJuIG9mIHRoZSBjZW50dXJ5IG9uIGlzIHN0YXJrbHkgYXZlcmFnZSBjb21wYXJlZCB0byBvbGRlciBob3VzZXMgd2hpY2ggYXJlIGNvbW1vbmx5IGluIGdvb2Qgc2hhcGUuIENoZWNraW5nIGFnYWluc3QgcmVtb2RlbCB5ZWFycyB3aWxsIGxpa2VseSBleHBsYWluIHNvbWUgb2YgdGhpcywgYnV0IEknbSBjdXJpb3VzIHRvIGNvbXBhcmUgZXh0ZXJpb3IgY29uZGl0aW9ucyBvZiBkaWZmZXJlbnQgbWF0ZXJpYWxzIHdpdGggcmVnYXJkIHRvIGFnZS4NCg0KVGhlIExvc3QgR2VuZXJhdGlvbiBnb3QgYnJpY2sgYW5kIHRpbGUgZm91bmRhdGlvbnMuIEJvb21lcnMgZ290IGNpbmRlciBibG9jayBhbmQgc2xhYnMuIEdlbiB4IGFuZCBNaWxsZW5pYWxzIGdvdCBwb3VyZWQgY29uY3JldGUuIFRoaXMgZW5hYmxlZCB1cyB0byBidWlsZCBpbnRvIHRoZSBzaWRlcyBvZiBoaWxscyBiZXR0ZXIgYW5kIGhhdmUgbW9yZSBleHBvc2VkIGJhc2VtZW50cyB3aXRoIHdhbGtvdXRzLg0KDQpNb3N0IG9mIHRoZSBiYXNlbWVudGxlc3MgaG91c2VzIHdlcmUgYnVpbHQgZHVyaW5nIHRoZSBwb3N0LVdXSUkgYm9vbSB3aGVuIHJhbWJsZXJzIHdlcmUgYSBjb21tb24gYW5zd2VyIHRvIHRoZSBidXJnZW9uaW5nIGRyZWFtIG9mIGhvbWVvd25lcnNoaXAuIEJhc2VtZW50IGFyZWEgY29udGludW91c2x5IGdyZXcgZnJvbSB0aGVuIG9uLCBlc3BlY2lhbGx5IGZpbmlzaGVkIGJhc2VtZW50IGFyZWEgYXMgcGVvcGxlIGJlZ2FuIHRvIGxpdmUgbW9yZSB1bmRlcmdyb3VuZC4gS2l0Y2hlbnMgYW5kIGJlZHJvb21zIGJlZ2FuIHRvIGJlIG1vcmUgY29tbW9uIGJlbG93IGdyb3VuZC4NCg0KS2l0Y2hlbnMgYmVjYW1lIG1vcmUgb2YgYSB0YXJnZXQgZm9yIGFkZGluZyB2YWx1ZSB3aXRoIGltcHJvdmVkIG1hdGVyaWFscywgY29uc3RydWN0aW9uLCBhbmQgYXBwbGlhbmNlcy4gSGVhdGluZyBzeXN0ZW1zIGltcHJvdmVkIG92ZXIgdGhlIHllYXJzIGFzIHdlbGwuIE5ldyBlbGVjdHJpY2FsIHN0YW5kYXJkcyBjYW1lIGludG8gcGxhY2UgaW4gMTk2MC4NCg0KV2hlbiB0aGUgJzgwcyBoaXQsIHRoZSBhdXRvbW9iaWxlZCBzb2NpZXR5IHdhcyBpbiBmdWxsIHN3aW5nLCBhbmQgaXQgd2FzIHJhcmUgdG8gc2VlIGxlc3MgdGhhbiBhIHR3by1jYXIgZ2FyYWdlIGJ1aWx0IGFueW1vcmUsIGxldCBhbG9uZSBhIGhvdXNlIHdpdGhvdXQgYSBnYXJhZ2UuIFJhdGhlciB0aGFuIGEgZGV0YWNoZWQgc2Vjb25kYXJ5IGJ1aWxkaW5nLCBnYXJhZ2VzIGJlY2FtZSBwYXJ0IG9mIHRoZSBtYWluIGhvdXNlIGl0c2VsZi4gSXQgd2FzIG1vcmUgb2Z0ZW4gYSBmaW5pc2hlZCBzcGFjZSBmb3IgbW9yZSB0aGFuIGp1c3QgaG91c2luZyBhbmQgd29ya2luZyBvbiBjYXJzLCBidXQgd2l0aG91dCB0aGUgbmVlZCBmb3IgaGlnaC1xdWFsaXR5IGNvbnN0cnVjdGlvbi4gVW5wYXZlZCBkcml2ZXdheXMgd2VyZSBub3cgb3V0IG9mIHRoZSBxdWVzdGlvbiwgdGhvdWdoLg0KDQpBZnRlciAyMDAwLCB2aXJ0dWFsbHkgbm8gbmV3IGhvdXNlcyBoYWQgZmVuY2VzLCBhdCBsZWFzdCBub25lIHRoYXQgd2VyZSBib3VnaHQgZHVyaW5nIHRoZSB5ZWFycyB0aGlzIGRhdGEgc2V0IGNvdmVycy4NCg0KIyMjIFllYXJCdWlsdCBhcyBGYWN0b3INCg0KSSdsbCBjb25kZW5zZSB0aGUgaG91c2luZyBib29tcyBhcm91bmQgdGhlaXIgbW9kZXMgaW4gYSBmYWN0b3IgdGhhdCBJIGNhbiBvbmUtaG90IGVuY29kZSB0byBhY2NvbW1vZGF0ZSBzb21lIE1MIGFsZ29yaXRobXMgdGhhdCB3b3VsZG4ndCBoYW5kbGUgYSBwb2x5bW9kYWwgZGlzdHJpYnV0aW9uIHdlbGwuIFRvIG1haW50YWluIHRoZSBtb2RlbCBnb2luZyBmb3J3YXJkLCBuZXcgcGVyaW9kcyB3aWxsIGhhdmUgdG8gYmUgaWRlbnRpZmllZCBhbmQgYWRkZWQuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgVGhyZWUgcGVyaW9kczogPCAxOTQ1IDwgMTk4NWlzaCA8IDIwMTANCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKA0KICAgIFllYXJCdWlsdC5mYWN0ID0gY3V0KA0KICAgICAgeCA9IFllYXJCdWlsdCwNCiAgICAgIGJyZWFrcyA9IGMoLUluZiwgMTk0NCwgMTk4NiwgSW5mKSwNCiAgICAgIG9yZGVyZWRfcmVzdWx0ID0gVCwNCiAgICAgIGxhYmVscyA9IGMoJ0JlZm9yZV8xOTQ1JywgJzE5NDVfMTk4NicsICdBZnRlcl8xOTg2JykNCiAgICApDQogICkNCg0KcDEgPSBnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IFllYXJCdWlsdCkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMTg4MCwgMjAxMCwgNSkpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkNCg0KcDIgPSBnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IFllYXJCdWlsdC5mYWN0KSkgKw0KICBnZW9tX2JhcigpDQoNCmdyaWQuYXJyYW5nZShwMSwgcDIpDQpgYGANCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdZZWFyQnVpbHQuZmFjdCcNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KeSA9ICdsb2coU2FsZVByaWNlKScNCnN1bV9hbmRfdHJhbnNfZmFjdChkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB6ID0geSwgbWluX24gPSAyOSkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXkgPSBGDQogICkNCg0KcHJpbnQoDQogICAgcGFzdGUoDQogICAgICAiTGV2ZWxzIHcvIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IiwNCiAgICAgIHksDQogICAgICAidGhhbiBhbm90aGVyIGxldmVsOiINCiAgICApDQogICkNCnByaW50KHBfdmFscyRzaWduaWZfbGV2ZWxzKQ0KYGBgDQoNCiMjIEFnZQ0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpJdCBtaWdodCBtYWtlIHNlbnNlIHRvIHRyYW5zZm9ybSB5ZWFyIGJ1aWx0IGludG8gdGhlIGFnZSBvZiB0aGUgaG91c2Ugd2hlbiBib3VnaHQuIFRoZSB5ZWFycyBvZiBzYWxlcyBpcyBhIHNtYWxsIHJhbmdlLCBidXQgaXQgbWF5IGFkZCBhIHRvdWNoIG1vcmUgcHJlZGljdGl2ZSBpbmZvcm1hdGlvbiwgZXNwZWNpYWxseSBmb3IgdGhlIG5ld2VyIGhvdXNlcy4NCg0KIyMjIE5vcm1hbGl6ZQ0KDQpJdCBzZWVtcyB0byBoYXZlIGFkZGVkIGEgY291cGxlIG9mIG91dGxpZXJzLCBidXQgYXBwbHlpbmcgYSBzcXVhcmUtcm9vdCB0cmFuc2Zvcm1hdGlvbiBiZXR0ZXIgY2VudGVycyBpdCBhbmQgcmVtb3ZlcyB0aGUgb3V0bGllcnMuDQoNCjBzIGRvbid0IGluZGljYXRlIGEgbWlzc2luZyBmZWF0dXJlLCBzbyBJIHdhbnQgdG8gaW5jbHVkZSB0aGVtIGluIHRoZSBzZWFyY2ggZm9yIHRoZSBiZXN0IG5vcm1hbGl6ZXIuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKCdBZ2UnID0gKFlyU29sZCAtIFllYXJCdWlsdCkpDQoNCnggPSAnQWdlJw0KDQojIEluY2x1ZGUgMHMuIEp1c3QgcmVjYWxjdWxhdGUgcmF0aGVyIHRoYW4gcmV3cml0ZSBjb2RlLg0KbnVtX2ZlYXRzID0gYyh4KQ0KYmVzdF9ub3JtYWxpemVycyA9IGZpbmRfYmVzdF9ub3JtYWxpemVyX3Blcl9mZWF0KA0KICBkZiA9IHZhbF90cmFpbl9YeSwNCiAgZmVhdHNfbHN0ID0gbnVtX2ZlYXRzLA0KICBmdW5jc19sc3QgPSBmdW5jc19sc3QsDQogIGV4Y2x1ZGVfdmFscyA9IGxpc3QoLTEpDQopDQoNCnN1bW1hcnkodmFsX3RyYWluX1h5W3hdKQ0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAxLA0KICB0X2JpbncgPSAxLzEwDQopDQpgYGANCg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ3NxcnQoQWdlKScNCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKCdzcXJ0KEFnZSknID0gc3FydChBZ2UpKQ0KDQojIEluY2x1ZGUgMHMuIEp1c3QgcmVjYWxjdWxhdGUgcmF0aGVyIHRoYW4gcmV3cml0ZSBjb2RlLg0KbnVtX2ZlYXRzID0gYyh4KQ0KYmVzdF9ub3JtYWxpemVycyA9IGZpbmRfYmVzdF9ub3JtYWxpemVyX3Blcl9mZWF0KA0KICBkZiA9IHZhbF90cmFpbl9YeSwNCiAgZmVhdHNfbHN0ID0gbnVtX2ZlYXRzLA0KICBmdW5jc19sc3QgPSBmdW5jc19sc3QsDQogIGV4Y2x1ZGVfdmFscyA9IGxpc3QoLTEpDQopDQoNCnN1bW1hcnkodmFsX3RyYWluX1h5W3hdKQ0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAxLzEwLA0KICB0X2JpbncgPSAxLzEwDQopDQoNCiMgUmVjYWxjdWxhdGUgYmVzdCBub3JtYWxpemVycy4NCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCmJlc3Rfbm9ybWFsaXplcnMgPSBmaW5kX2Jlc3Rfbm9ybWFsaXplcl9wZXJfZmVhdCgNCiAgZGYgPSB2YWxfdHJhaW5fWHksDQogIGZlYXRzX2xzdCA9IG51bV9mZWF0cywNCiAgZnVuY3NfbHN0ID0gZnVuY3NfbHN0LA0KICBleGNsdWRlX3ZhbHMgPSBsaXN0KDApDQopDQpgYGANCg0KIyMjIENvcnJlbGF0aW9ucw0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpudW1fZmVhdHMgPSBjb2xuYW1lcyhzZWxlY3QodmFsX3RyYWluX1h5LCB3aGVyZShpcy5udW1lcmljKSkpDQp4X2xzdCA9IGMoJ1llYXJCdWlsdCcsICdBZ2UnLCAnc3FydChBZ2UpJykNCg0KZGYgPSBnZXRfY29ycygNCiAgZGF0YSA9IGZpbHRlcigNCiAgICBzZWxlY3QodmFsX3RyYWluX1h5LCBhbGxfb2YobnVtX2ZlYXRzKSksDQogICAgIWlzLm5hKC5kYXRhW1t4XV0pDQogICksDQogIHhfbHN0ID0geF9sc3QsDQogIGZlYXRzID0gbnVtX2ZlYXRzDQopDQpkZg0KcHJpbnQoIlN1bW1hcnkgb2YgYWJzb2x1dGUgdmFsdWVzIG9mIFBlYXJzb24ncyBSczoiKQ0KZGYgPSBhYnMoZGYpDQpzdW1tYXJ5KGFicyhkZikpDQoNCmRmID0gbWVsdChkZikNCmdncGxvdChkZiwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHZhbHVlKSkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArDQogIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMnKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnlfbHN0ID0gYygnbG9nKFNhbGVQcmljZSknLCAnV2luKGxvZyhTYWxlUHJpY2UpKScsICdHYXJhZ2VBcmVhJywgJ0dhcmFnZUNhcnMnLA0KICAgICAgICAgICdFbmNsb3NlZFBvcmNoJywgJ09wZW5Qb3JjaFNGJywgJ092ZXJhbGxRdWFsX2ludCcsICdPdmVyYWxsQ29uZF9pbnQnKQ0KcGxvdF9zY2F0X3BhaXJzKGRmID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeV9sc3QgPSB5X2xzdCkNCmBgYA0KDQpUaGVyZSdzIGEgc21hbGwgaW5jcmVhc2UgaW4gbGluZWFyIGNvcnJlbGF0aW9uIG9mIEFnZSB0byB0cmFuc2Zvcm1lZCBTYWxlUHJpY2UgZnJvbSBZZWFyQnVpbHQgKDAuNTkgdG8gMC42MikuIENvcnJlbGF0aW9ucyB0byBzb21lIG1lYXN1cmVzIG9mIHNpemUgc3RyZW5ndGhlbmVkLiBUaGUgY29ycmVsYXRpb24gdG8gcXVhbGl0eSByb3NlIHF1aXRlIGEgYml0IChmcm9tIDAuNTkgdG8gMC42NSkgd2hpbGUgdGhlIGNvcnJlbGF0aW9uIHRvIGNvbmRpdGlvbiBvbmx5IHJvc2Ugc2xpZ2h0bHkgKDAuMzYgdG8gMC4zNyksIHJlbWFpbmluZyB3ZWFrLg0KDQpXaXRoIFllYXJCdWlsdCwgdGhlcmUncyBzdGlsbCBhIHBvbHlub21pYWwgYXBwZWFyYW5jZSB0byB0aGUgcGxvdCBhZ2FpbnN0IGxvZyhTYWxlUHJpY2UpLCBidXQgdGhhdCdzIHNtb290aGVkIG91dCB3aXRoIHNxcnQoQWdlKS4gWWVhclJlbW9kQWRkIG1heSBhZGQgY2xhcml0eS4NCg0KPGEgaWQ9ImNvbmRBZ2UiPjwvYT4NCg0KVGhlIGNvbmNlbnRyYXRpb24gb2YgYXZlcmFnZS1jb25kaXRpb24gaG91c2VzIGFtb25nIHRoZSB5b3VuZ2VzdCBpcyBzdGlsbCBwdXp6bGluZy4gSXQncyBhcyBpZiBhIGZldyB5ZWFycyBvZiBhZ2luZyB0ZW5kcyB0byBpbXByb3ZlIHRoZSBjb25kaXRpb24gb2YgYSBob3VzZS4gUGVyaGFwcyBvd25lcnMgdGVuZCB0byBhZGQgY29zbWV0aWMgdmFsdWUgaW4gdGhlIGZpcnN0IHllYXJzLiBNYXliZSBhc3Nlc3NvcnMgdXNlIHRoZSB5b3VuZ2VzdCBob3VzZXMgYXMgdGhlIGFuY2hvciBieSB3aGljaCB0byBhc3Nlc3MgYWxsIG90aGVycy4gTWFueSBvZiB0aGVzZSBob3VzZXMgd2VyZSB1bmZpbmlzaGVkLCB3aGljaCBleHBsYWlucyBzb21lIGJ1dCBub3QgYWxsIG9mIHRoaXMuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmdncGxvdCgNCiAgdmFsX3RyYWluX1h5LA0KICBhZXMoDQogICAgeCA9IC5kYXRhW1snc3FydChBZ2UpJ11dLA0KICAgIHkgPSBPdmVyYWxsQ29uZF9pbnQsDQogICAgc2hhcGUgPSBTYWxlQ29uZGl0aW9uLA0KICAgIGNvbG9yID0gU2FsZUNvbmRpdGlvbg0KICApDQopICsNCiAgZ2VvbV9qaXR0ZXIoKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHNlbGVjdCh2YWxfdHJhaW5fWHksIC1jKCdBZ2UnKSkNCmBgYA0KDQojIyBZZWFyUmVtb2RBZGQNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdZZWFyUmVtb2RBZGQnDQpzdW1tYXJ5KHZhbF90cmFpbl9YeVt4XSkNCnN1bV9hbmRfdHJhbnNfY29udCgNCiAgZGF0YSA9IHZhbF90cmFpbl9YeSwNCiAgeCA9IHgsDQogIGZ1bmMgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJGZ1bmMsDQogIGZ1bmNfbmFtZSA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkbmFtZSwNCiAgeF9iaW53ID0gMSwNCiAgdF9iaW53ID0gMQ0KKQ0KYGBgDQoNClRoZXJlIHdhcyBhIGN1cmlvdXNseSBsYXJnZSBudW1iZXIgb2YgcmVtb2RlbHMgaW4gMTk1MCwgYWJvdXQgODAgcmVtb2RlbHMgdGhhdCB5ZWFyIGNvbXBhcmVkIHRvIHRoZSBwZWFrIG9mIGFib3V0IDUwIGluIHRoZSAyMDAwcy4gVGhlc2Ugd2VyZSBhbGwgYnVpbHQgYmVmb3JlIDE5NTAsIGFuZCBubyBob3VzZSBoYXMgYW4gZWFybGllciByZW1vZGVsIHllYXIgdGhhbiAxOTUwLCBhbmQgc29tZSBob3VzZXMgYnVpbHQgYmVmb3JlIDE5NTAgaGF2ZSByZW1vZGVsIHllYXJzIGxhdGVyIHRoYW4gMTk1MC4NCg0KSSdsbCB0cmVhdCBob3VzZXMgd2l0aCBhIDE5NTAgcmVtb2RlbCBhcyBpZiB0aGV5IGhhdmUgbm90IGhhZCBhIHJlbW9kZWw7IHRoZXkgbWF5IGhhdmUgaGFkIGFuIGVhcmxpZXIgcmVtb2RlbCwgYnV0IEknbSBndWVzc2luZyB0aGF0IHRob3NlIG9sZGVyIHJlbW9kZWxzIGFyZSBvZiBsaXR0bGUgYWRkZWQgdmFsdWUgYXQgdGhpcyBwb2ludC4gQW1lcyBhc3Nlc3NvcnMgbWF5IGhhdmUgaGFkIHJlYXNvbiB0byBib3R0b20tY29kZSwgYnV0IEknbGwgc2V0IHJlbW9kZWwgeWVhciB0byBidWlsdCB5ZWFyIGluIHllYXJzIHByaW9yIHRvIDE5NTAgZm9yIG5vdyBhbmQgc2VlIGlmIGl0IGhlbHBzIG9yIGhpbmRlcnMgYW5hbHlzaXMgYW5kIHByZWRpY3Rpb24uDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKFllYXJSZW1vZEFkZC51bmNvZGUgPSBpZmVsc2UoWWVhclJlbW9kQWRkID09IDE5NTAsIFllYXJCdWlsdCwgWWVhclJlbW9kQWRkKSkNCnggPSAnWWVhclJlbW9kQWRkLnVuY29kZScNCiMgUmVjYWxjdWxhdGUgYmVzdCBub3JtYWxpemVycy4NCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCmJlc3Rfbm9ybWFsaXplcnMgPSBmaW5kX2Jlc3Rfbm9ybWFsaXplcl9wZXJfZmVhdCgNCiAgZGYgPSB2YWxfdHJhaW5fWHksDQogIGZlYXRzX2xzdCA9IG51bV9mZWF0cywNCiAgZnVuY3NfbHN0ID0gZnVuY3NfbHN0LA0KICBleGNsdWRlX3ZhbHMgPSBsaXN0KDApDQopDQoNCnN1bW1hcnkodmFsX3RyYWluX1h5W3hdKQ0KIyBzdW1fYW5kX3RyYW5zX2NvbnQoDQojICAgZGF0YSA9IHZhbF90cmFpbl9YeSwNCiMgICB4ID0geCwNCiMgICBmdW5jID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRmdW5jLA0KIyAgIGZ1bmNfbmFtZSA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkbmFtZSwNCiMgICB4X2JpbncgPSAxLA0KIyAgIHRfYmludyA9IDENCiMgKQ0KZ2dwbG90KHZhbF90cmFpbl9YeSwgYWVzKHggPSAuZGF0YVtbeF1dKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEpDQpgYGANCg0KIyMjIENvcnJlbGF0aW9ucw0KDQpUaGlzIG9ubHkgYWRkZWQgb3V0bGllcnMgYW5kIHNsaWdodGx5IHdlYWtlbmVkIHRoZSBjb3JyZWxhdGlvbiB0byBTYWxlUHJpY2UuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCnhfbHN0ID0gYygnWWVhckJ1aWx0JywgJ1llYXJSZW1vZEFkZCcsICdZZWFyUmVtb2RBZGQudW5jb2RlJykNCg0KZGYgPSBnZXRfY29ycygNCiAgZGF0YSA9IGZpbHRlcigNCiAgICBzZWxlY3QodmFsX3RyYWluX1h5LCBhbGxfb2YobnVtX2ZlYXRzKSksDQogICAgIWlzLm5hKC5kYXRhW1t4XV0pDQogICksDQogIHhfbHN0ID0geF9sc3QsDQogIGZlYXRzID0gbnVtX2ZlYXRzDQopDQpkZg0KcHJpbnQoIlN1bW1hcnkgb2YgYWJzb2x1dGUgdmFsdWVzIG9mIFBlYXJzb24ncyBSczoiKQ0KZGYgPSBhYnMoZGYpDQpzdW1tYXJ5KGFicyhkZikpDQoNCmRmID0gbWVsdChkZikNCmdncGxvdChkZiwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHZhbHVlKSkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArDQogIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMnKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnlfbHN0ID0gYygnbG9nKFNhbGVQcmljZSknKQ0KcGxvdF9zY2F0X3BhaXJzKGRmID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeV9sc3QgPSB5X2xzdCkNCmBgYA0KDQojIyMgQnkgRmFjdG9ycw0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp5X2xzdCA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLmZhY3RvcikpKQ0KZm9yICh5IGluIHlfbHN0KSB7DQogIHBsdCA9IGZlbmNlZF9qYnYoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHksIHkgPSB4KSArDQogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkNCiAgcHJpbnQocGx0KQ0KfQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHNlbGVjdCh2YWxfdHJhaW5fWHksIC1jKCdZZWFyUmVtb2RBZGQudW5jb2RlJykpDQpgYGANCg0KUmVtb3ZpbmcgdGhlIHJlY29yZHMgaW4gd2hpY2ggdGhlcmUgaXMgbm8gcmVtb2RlbCAoaS5lLiBZZWFyUmVtb2RBZGQgPT0gWWVhckJ1aWx0IG9yIDE5NTApIGFkZHMgY2xhcml0eS4gMzY4IHJvd3Mgd2VyZSByZW1vdmVkIGZvciBubyByZW1vZGVsLCBhbmQgcmVtb2RlbHMgcmVhbGx5IHN0YXJ0ZWQgYmVpbmcgcmVjb3JkZWQgaW4gaW5jcmVhc2luZyBudW1iZXJzIHN0YXJ0aW5nIGluIHRoZSAnOTBzLCBtYXliZSBhIGxpdHRsZSBpbiB0aGUgbGF0ZSAnODBzLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnZ3Bsb3QoDQogIHZhbF90cmFpbl9YeSwNCiAgYWVzKHggPSBpZmVsc2UoWWVhclJlbW9kQWRkID09IFllYXJCdWlsdCwgTkEsIFllYXJSZW1vZEFkZCkNCiAgKQ0KKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSkNCmBgYA0KDQojIyMgWWVhclJlbW9kQWRkIGFzIEZhY3Rvcg0KDQpCZWNhdXNlIHRoZXJlIGFyZSBzbyBtYW55IGhvdXNlcyB3aXRob3V0IHJlbW9kZWxzLCBJJ2xsIHNwbGl0IGl0IGludG8gYSBmYWN0b3IsIGEgbGV2ZWwgZm9yIGVhY2ggZGVjYWRlIHdpdGggdGhlIHllYXIgMTk1MCBjb252ZW5pZW50bHkgbHVtcGVkIGluIHdpdGggTkFzLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ1llYXJSZW1vZEFkZC5mYWN0Jw0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoDQogICAgWWVhclJlbW9kQWRkLmZhY3QgPSBmYWN0b3IoDQogICAgICBjdXQoDQogICAgICAgIGlmZWxzZSgNCiAgICAgICAgICBZZWFyUmVtb2RBZGQgPT0gWWVhckJ1aWx0IHwgaXMubmEoWWVhclJlbW9kQWRkKSwNCiAgICAgICAgICAxOTQ5LA0KICAgICAgICAgIFllYXJSZW1vZEFkZA0KICAgICAgICApLA0KICAgICAgICBicmVha3MgPSBjKDE5NDksIDE5NTAsIDE5NjAsIDE5NzAsIDE5ODAsIDE5OTAsIDIwMDAsIDIwMTApLA0KICAgICAgICBsYWJlbHMgPSBjKCdOb25lJywgJzUwcycsICc2MHMnLCAnNzBzJywgJzgwcycsICc5MHMnLCAnMDBzJykNCiAgICAgICkNCiAgICApDQogICkgIyU+JQ0KICAjIHNlbGVjdCgtYygnWWVhclJlbW9kQWRkJykpDQoNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KDQpwX3ZhbHMgPSBnZXRfc2lnbmlmX2xldmVscyhkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeiA9IHksIG1pbl9uID0gMjkpDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCmBgYA0KDQojIyBSb29mU3R5bGUNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KTW9zdCBhcmUgR2FibGUgKDU2MyksIGFuZCBtYW55IGFyZSBIaXAgKDEzOSkuIEZsYXQsIEdhbWJyZWwsIGFuZCBNYW5zYXJkIGFyZSBuZWdsaWJsZS4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdSb29mU3R5bGUnDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5LCBtaW5fbiA9IDI5KQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQpgYGANCg0KIyMgUm9vZk1hdGwNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KVmFzdCBtYWpvcml0eSBhcmUgY29tcG9zaXRpb24gc2hpbmdsZSAoNzAyKS4gQ2FuIGRyb3AgdGhpcyBmZWF0dXJlLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzdW1tYXJ5KHZhbF90cmFpbl9YeSRSb29mTWF0bCkNCnZhbF90cmFpbl9YeSA9IHNlbGVjdCh2YWxfdHJhaW5fWHksIC1jKCdSb29mTWF0bCcpKQ0KYGBgDQoNCiMjIEV4dGVyaW9yMXN0LzJuZA0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpNb3N0IHBvcHVsYXIgY2xhc3MgaXMgdmlueWwgKDI1NSBhbmQgMjQ5KSwgYnV0IHdvb2QsIG1ldGFsLCBhbmQgb3RoZXJzIHJlcHJlc2VudCBzaWduaWZpY2FudCBjbGFzc2VzLiBObyAnTm9uZScgaW4gMm5kLCBzbyBhbGwgaG91c2VzIGluIEFtZXMgaGF2ZSBhdCBsZWFzdCB0d28gdHlwZXMgb2Ygc2lkaW5nPw0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ0V4dGVyaW9yMXN0Jw0KeSA9ICdTYWxlUHJpY2UnDQpzdW1tYXJpemVfYnkoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KeSA9ICdsb2coU2FsZVByaWNlKScNCnN1bV9hbmRfdHJhbnNfZmFjdChkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB6ID0geSwgbWluX24gPSAyOSkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXkgPSBGDQogICkNCg0KcHJpbnQoDQogICAgcGFzdGUoDQogICAgICAiTGV2ZWxzIHcvIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IiwNCiAgICAgIHksDQogICAgICAidGhhbiBhbm90aGVyIGxldmVsOiINCiAgICApDQogICkNCnByaW50KHBfdmFscyRzaWduaWZfbGV2ZWxzKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnRXh0ZXJpb3IybmQnDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5LCBtaW5fbiA9IDI5KQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQpgYGANCg0KIyMgTWFzVm5yVHlwZQ0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpNb3N0IGhhdmUgbm9uZSAoNDMwKSwgYnV0IHBsZW50eSBvZiBicmljayAoMjE5KSBhbmQgc3RvbmUgKDY2KS4gTm8gY2luZGVyYmxvY2sgaW4gdGhlIHNwbGl0Lg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ01hc1ZuclR5cGUnDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5LCBtaW5fbiA9IDI5KQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQpgYGANCg0KIyMgTWFzVm5yQXJlYQ0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQojIyMgTm9ybWFsaXplDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnTWFzVm5yQXJlYScNCnN1bW1hcnkodmFsX3RyYWluX1h5W3hdKQ0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5W3ZhbF90cmFpbl9YeSRNYXNWbnJBcmVhICE9IDAsIF0sDQogIHggPSB4LA0KICBmdW5jID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRmdW5jLA0KICBmdW5jX25hbWUgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJG5hbWUsDQogIHhfYmludyA9IDEwLA0KICB0X2JpbncgPSAwLjI1DQopDQpgYGANCg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ2NicnQoTWFzVm5yQXJlYSknDQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgnY2JydChNYXNWbnJBcmVhKScgPSBNYXNWbnJBcmVhXigxLzMpKQ0KDQojIFJlY2FsY3VsYXRlIGJlc3Qgbm9ybWFsaXplcnMuDQpudW1fZmVhdHMgPSBjb2xuYW1lcyhzZWxlY3QodmFsX3RyYWluX1h5LCB3aGVyZShpcy5udW1lcmljKSkpDQpiZXN0X25vcm1hbGl6ZXJzID0gZmluZF9iZXN0X25vcm1hbGl6ZXJfcGVyX2ZlYXQoDQogIGRmID0gdmFsX3RyYWluX1h5LA0KICBmZWF0c19sc3QgPSBudW1fZmVhdHMsDQogIGZ1bmNzX2xzdCA9IGZ1bmNzX2xzdCwNCiAgZXhjbHVkZV92YWxzID0gbGlzdCgwKQ0KKQ0KDQpzdW1tYXJ5KHZhbF90cmFpbl9YeVt4XSkNCnN1bV9hbmRfdHJhbnNfY29udCgNCiAgZGF0YSA9IHZhbF90cmFpbl9YeVt2YWxfdHJhaW5fWHkkTWFzVm5yQXJlYSAhPSAwLCBdLA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAwLjI1LA0KICB0X2JpbncgPSAwLjI1DQopDQpgYGANCg0KIyMjIFdpbnNvcml6ZQ0KDQpCZWNhdXNlIHRoaXMgdmFyaWFibGUncyAwcyBpbmRpY2F0ZSBhIG1pc3NpbmcgZmVhdHVyZSwgd2UncmUgcmVhbGx5IGNvbmNlcm5lZCB3aXRoIFdpbnNvcml6aW5nIG5vbi16ZXJvIHZhbHVlcy4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGYgPSB2YWxfdHJhaW5fWHlbdmFsX3RyYWluX1h5JE1hc1ZuckFyZWEgIT0gMCwgXQ0KDQpxcW5vcm0oeSA9IGRmJE1hc1ZuckFyZWEsIHlsYWIgPSAnTWFzVm5yQXJlYScpDQpxcWxpbmUoeSA9IGRmJE1hc1ZuckFyZWEsIHlsYWIgPSAnTWFzVm5yQXJlYScpDQoNCnFxbm9ybSh5ID0gZGZbW3hdXSwgeWxhYiA9IHgpDQpxcWxpbmUoeSA9IGRmW1t4XV0sIHlsYWIgPSB4KQ0KDQpxcW5vcm0oeSA9IHNxcnQoc3FydChkZiRNYXNWbnJBcmVhKSksIHlsYWIgPSAnc3FydChzcXJ0KE1hc1ZuckFyZWEpKScpDQpxcWxpbmUoeSA9IHNxcnQoc3FydChkZiRNYXNWbnJBcmVhKSksIHlsYWIgPSAnc3FydChzcXJ0KE1hc1ZuckFyZWEpKScpDQoNCldpbl9zcXJ0X3ggPSBXaW5zb3JpemUoDQogIHggPSBzcXJ0KHNxcnQoZGYkTWFzVm5yQXJlYSkpLA0KICBwcm9icyA9IGMoMC4wMDUsIDAuOTk1KSwNCiAgbmEucm0gPSBUDQopDQoNCnFxbm9ybSh5ID0gV2luX3NxcnRfeCwgeWxhYiA9ICdXaW4oc3FydChzcXJ0KE1hc1ZuckFyZWEpKSknKQ0KcXFsaW5lKHkgPSBXaW5fc3FydF94LCB5bGFiID0gJ1dpbihzcXJ0KHNxcnQoTWFzVm5yQXJlYSkpKScpDQoNCldpbl9jYnJ0X3ggPSBXaW5zb3JpemUoDQogIHggPSBkZiRgY2JydChNYXNWbnJBcmVhKWAsDQogIHByb2JzID0gYygwLjAwNSwgMC45OTUpLA0KICBuYS5ybSA9IFQNCikNCg0KcXFub3JtKHkgPSBXaW5fY2JydF94LCB5bGFiID0gJ1dpbihjYnJ0KE1hc1ZuckFyZWEpKScpDQpxcWxpbmUoeSA9IFdpbl9jYnJ0X3gsIHlsYWIgPSAnV2luKGNicnQoTWFzVm5yQXJlYSkpJykNCg0KV2luX3Jhd194ID0gV2luc29yaXplKA0KICB4ID0gZGYkTWFzVm5yQXJlYSwNCiAgcHJvYnMgPSBjKDAuMDUsIDAuOTUpLA0KICBuYS5ybSA9IFQNCikNCg0KcXFub3JtKHkgPSBXaW5fcmF3X3gsIHlsYWIgPSAnV2luKE1hc1ZuckFyZWEpJykNCnFxbGluZSh5ID0gV2luX3Jhd194LCB5bGFiID0gJ1dpbihNYXNWbnJBcmVhKScpDQoNCnByaW50KHNoYXBpcm8udGVzdChkZiRNYXNWbnJBcmVhKSkNCnByaW50KHNoYXBpcm8udGVzdChkZiQnY2JydChNYXNWbnJBcmVhKScpKQ0KcHJpbnQoc2hhcGlyby50ZXN0KHNxcnQoc3FydChkZiRNYXNWbnJBcmVhKSkpKQ0KcHJpbnQoc2hhcGlyby50ZXN0KFdpbl9zcXJ0X3gpKQ0KcHJpbnQoc2hhcGlyby50ZXN0KFdpbl9jYnJ0X3gpKQ0KcHJpbnQoc2hhcGlyby50ZXN0KFdpbl9yYXdfeCkpDQpgYGANCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbWluX3ZhbCA9IG1pbihXaW5fY2JydF94KQ0KbWF4X3ZhbCA9IG1heChXaW5fY2JydF94KQ0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoDQogICAgJ1dpbihjYnJ0KE1hc1ZuckFyZWEpKScgPSBpZmVsc2UoDQogICAgICBNYXNWbnJBcmVhID09IDAsDQogICAgICAwLA0KICAgICAgV2luc29yaXplKA0KICAgICAgICB4ID0gYGNicnQoTWFzVm5yQXJlYSlgLA0KICAgICAgICAjIHByb2JzID0gYygwLjAwNSwgMC45OTUpLA0KICAgICAgICBtaW52YWwgPSBtaW5fdmFsLA0KICAgICAgICBtYXh2YWwgPSBtYXhfdmFsDQogICAgICApDQogICAgKQ0KICApDQpgYGANCg0KIyMjIENvcnJlbGF0aW9ucw0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ1dpbihjYnJ0KE1hc1ZuckFyZWEpKScNCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCnhfbHN0ID0gYygnTWFzVm5yQXJlYScsICdjYnJ0KE1hc1ZuckFyZWEpJywgeCkNCg0KZGYgPSBnZXRfY29ycygNCiAgZGF0YSA9IGZpbHRlcigNCiAgICBzZWxlY3QodmFsX3RyYWluX1h5LCBhbGxfb2YobnVtX2ZlYXRzKSksDQogICAgLmRhdGFbW3hdXSAhPSAwDQogICksDQogIHhfbHN0ID0geF9sc3QsDQogIGZlYXRzID0gbnVtX2ZlYXRzDQopDQpkZg0KcHJpbnQoIlN1bW1hcnkgb2YgYWJzb2x1dGUgdmFsdWVzIG9mIFBlYXJzb24ncyBScyAobm8gMHMpOiIpDQpkZiA9IGFicyhkZikNCnN1bW1hcnkoYWJzKGRmKSkNCg0KZGYgPSBtZWx0KGRmKQ0KZ2dwbG90KGRmLCBhZXMoeCA9IHZhcmlhYmxlLCB5ID0gdmFsdWUpKSArDQogIGdlb21fYm94cGxvdChub3RjaCA9IFQpICsNCiAgeWxhYihsYWJlbCA9ICdBYnNvbHV0ZSBWYWx1ZSBvZiBDb3JyZWxhdGlvbiB0byBPdGhlciBGZWF0dXJlcyAobm8gMHMpJykNCg0KZGYgPSBnZXRfY29ycygNCiAgZGF0YSA9IGZpbHRlcigNCiAgICBzZWxlY3QodmFsX3RyYWluX1h5LCBhbGxfb2YobnVtX2ZlYXRzKSksDQogICAgIWlzLm5hKC5kYXRhW1t4XV0pDQogICksDQogIHhfbHN0ID0geF9sc3QsDQogIGZlYXRzID0gbnVtX2ZlYXRzDQopDQpkZg0KcHJpbnQoIlN1bW1hcnkgb2YgYWJzb2x1dGUgdmFsdWVzIG9mIFBlYXJzb24ncyBSczoiKQ0KZGYgPSBhYnMoZGYpDQpzdW1tYXJ5KGFicyhkZikpDQoNCmRmID0gbWVsdChkZikNCmdncGxvdChkZiwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHZhbHVlKSkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArDQogIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMnKQ0KDQp5X2xzdCA9IGMoJ2xvZyhTYWxlUHJpY2UpJykNCmZvciAoZmVhdCBpbiB4X2xzdCkgew0KIHBsb3Rfc2NhdF9wYWlycyhkZiA9IHZhbF90cmFpbl9YeSwgeCA9IGZlYXQsIHlfbHN0ID0geV9sc3QpIA0KfQ0KYGBgDQoNClRoaXMgdmFyaWFibGUgaGFzIGEgbG90IG9mIHplcm9zIHRoYXQgaW5kaWNhdGUgYSBtaXNzaW5nIGZlYXR1cmUgdGhhdCBtYWtlIGl0IGEgY2FuZGlkYXRlIGZvciBiaW5hcnkgZW5jb2RpbmcgdG8gYWlkIGxpbmVhciByZWdyZXNzaW9uLiBCdXQsIE1hc1ZuclR5cGUgYWxyZWFkeSBlbmNvZGVzIHRoYXQgaW4gJ05vbmUnLiBCZWNhdXNlIHRoZXJlJ3MgYSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGluIHByaWNlIGJldHdlZW4gTWFzVm5yVHlwZSBsZXZlbHMsIHRoYXQgc2hvdWxkIHN1ZmZpY2UuIEFsc28sIGxlYXZpbmcgdGhlIDBzIGluIGltcHJvdmVzIHRoZSBjb3JyZWxhdGlvbiB0byB0aGUgdGFyZ2V0IHZhcmlhYmxlLCBzbyBpdCBtYXkgYmUgbW9vdC4NCg0KIyMjIEhhcmQgQ29kZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIGFscmVhZHkgaGFyZGNvZGVkIHRoaXMgb25lLg0KDQpwcmludChwYXN0ZSgibWluX3ZhbDoiLCBtaW5fdmFsKSkNCnByaW50KHBhc3RlKCJtYXhfdmFsOiIsIG1heF92YWwpKQ0KDQpnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IC5kYXRhW1t4XV0pKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gLjIpDQpgYGANCg0KIyMjIEJ5IEZhY3RvcnMNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeV9sc3QgPSBjKCdNYXNWbnJUeXBlJykNCnogPSAnU2FsZVByaWNlLmZhY3QnDQpmb3IgKHkgaW4geV9sc3QpIHsNCiAgcGx0ID0gZmVuY2VkX2pidigNCiAgICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICAgIHggPSB5LA0KICAgIHkgPSAnY2JydChNYXNWbnJBcmVhKScsDQogICAgaml0X2NvbCA9IHosDQogICAgaml0X2FscGhhID0gMC43NSwNCiAgICBsZWdfbGIgPSB6LA0KICAgIGJveF9jb2xvciA9ICdyZWQnDQogICkgKw0KICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0PTEpKQ0KICBwcmludChwbHQpDQp9DQoNCmdncGxvdCh2YWxfdHJhaW5fWHksIGFlcyh4ID0gYGNicnQoTWFzVm5yQXJlYSlgLCB5ID0gYGxvZyhTYWxlUHJpY2UpYCkpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nKSArDQogIGZhY2V0X3dyYXAodmFycyhNYXNWbnJUeXBlKSkNCmBgYA0KVGhlcmUgYXJlIHNvbWUgaG91c2VzIHdpdGggbWFzb25yeSBmb290YWdlIGJ1dCBubyB0eXBlIGFzY3JpYmVkLiBJIG5vdGljZWQgdGhpcyBkdXJpbmcgdGhlIHdyYW5nbGluZyBhdWRpdCBhbmQgZGVjaWRlZCB0byBsZWF2ZSBpdCBmb3IgdGhlIG1vZGVsaW5nIHBoYXNlLCBtYXliZSB0byBpbXB1dGUgdGhlIG1hc29ucnkgdHlwZSB3aXRoIGNhcmV0IHVzaW5nIGEgc2VsZWN0IGZldyB2YXJpYWJsZXMgYXMgaW5kaWNhdG9ycyBvciBzaW1wbHkgaW1wdXRpbmcgdGhlIG1vc3QgY29tbW9uIHR5cGUgKEJya0ZhY2UpLCBidXQgbWF5YmUgY3JlYXRlIGEgbmV3IGxldmVsIGZvciB0aG9zZSBoYW5kZnVsIG9mIHVuZGVzY3JpYmVkIG1hc29ucnkgdHlwZXMuDQoNCiMjIEV4dGVyUXVhbA0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpNb3N0bHkgYXZlcmFnZSAoNDQwKSwgbWFueSBnb29kICgyNDEpLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ0V4dGVyUXVhbCcNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KDQpwX3ZhbHMgPSBnZXRfc2lnbmlmX2xldmVscyhkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeiA9IHksIG1pbl9uID0gMjkpDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCmBgYA0KDQojIyBFeHRlckNvbmQNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KR3JlYXRlciBtYWpvcml0eSBhdmVyYWdlICg2MjEpLCBzdGlsbCBzb21lIGdvb2QgKDc0KS4gTWF5IGJlIHdvcnRoIGtlZXBpbmcuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnRXh0ZXJDb25kJw0KeSA9ICdTYWxlUHJpY2UnDQpzdW1tYXJpemVfYnkoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KeSA9ICdsb2coU2FsZVByaWNlKScNCnN1bV9hbmRfdHJhbnNfZmFjdChkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB6ID0geSwgbWluX24gPSAyOSkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXkgPSBGDQogICkNCg0KcHJpbnQoDQogICAgcGFzdGUoDQogICAgICAiTGV2ZWxzIHcvIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IiwNCiAgICAgIHksDQogICAgICAidGhhbiBhbm90aGVyIGxldmVsOiINCiAgICApDQogICkNCnByaW50KHBfdmFscyRzaWduaWZfbGV2ZWxzKQ0KYGBgDQoNCiMjIEZvdW5kYXRpb24NCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KRXZlbmx5IHNwbGl0IGJldHdlZW4gcG91cmVkIGNvbmNyZXRlIGFuZCBjaW5kZXIgYmxvY2sgKDMxNyBhbmQgMzAyKSwgYnV0IHN0aWxsIDcyIGJyaWNrIGFuZCB0aWxlLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ0ZvdW5kYXRpb24nDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5LCBtaW5fbiA9IDI5KQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQpgYGANCg0KQ291bGQgZHJvcCBTdG9uZSBhbmQgV29vZCBhbmQgbWFrZSBpdCBhbiBvcmRlcmVkIGZhY3RvciB0byByZXByZXNlbnQgYXMgaW50cyBpbiByZWdyZXNzaW9uIG9yIHRvIG9uZS1ob3QuDQoNCiMjIEJzbXRRdWFsDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNCkV2ZW5seSBzcGxpdCBiZXR3ZWVuIGF2ZXJhZ2UgYW5kIGdvb2QgKDMxMyBhbmQgMzAzKSwgYnV0IDYzIGV4Y2VsbGVudCwgYW5kIG9ubHkgMjYgd2l0aG91dCBiYXNlbWVudHMuIEknbSBoYXZpbmcgdHJvdWJsZSBpbWFnaW5pbmcgaG91c2VzIHdpdGggYmFzZW1lbnRzIGFuZCBjaW5kZXIgYmxvY2sgZm91bmRhdGlvbnMuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnQnNtdFF1YWwnDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5LCBtaW5fbiA9IDI5KQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQpgYGANCg0KIyMgQnNtdENvbmQNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KVmFzdCBtYWpvcml0eSBhdmVyYWdlICg2MzUpLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ0JzbXRDb25kJw0KeSA9ICdTYWxlUHJpY2UnDQpzdW1tYXJpemVfYnkoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KeSA9ICdsb2coU2FsZVByaWNlKScNCnN1bV9hbmRfdHJhbnNfZmFjdChkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB6ID0geSwgbWluX24gPSAyOSkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXkgPSBGDQogICkNCg0KcHJpbnQoDQogICAgcGFzdGUoDQogICAgICAiTGV2ZWxzIHcvIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IiwNCiAgICAgIHksDQogICAgICAidGhhbiBhbm90aGVyIGxldmVsOiINCiAgICApDQogICkNCnByaW50KHBfdmFscyRzaWduaWZfbGV2ZWxzKQ0KYGBgDQoNCiMjIEJzbXRFeHBvc3VyZQ0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpHcmVhdCBtYWpvcml0eSAoYWJvdXQgNDY0KSBub3QgZXhwb3NlZCwgYnV0IHN0aWxsIHNvbWUgYXZlcmFnZSAoMTAwKSwgZ29vZCAoNzEpLCBhbmQgbWluaW1hbCBleHBvc3VyZSAoNTQpLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ0JzbXRFeHBvc3VyZScNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KDQpwX3ZhbHMgPSBnZXRfc2lnbmlmX2xldmVscyhkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeiA9IHksIG1pbl9uID0gMjkpDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCmBgYA0KDQojIyBCc210RmluVHlwZTENCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KMjExIGFyZSB1bmZpbmlzaGVkLCAyMDUgYXJlIHRvcCBxdWFsaXR5LCBhbmQgZGVzY2VuZGluZyBjb3VudHMgdG8gbG93IHF1YWxpdHkgKDM0KS4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdCc210RmluVHlwZTEnDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5LCBtaW5fbiA9IDI5KQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQpgYGANCg0KUHJvYmFibHkganVzdCBvbmUtaG90IEdMUSwgTHdRLCBhbmQgTm9uZSBmb3IgcmVncmVzc2lvbi4NCg0KIyMgQnNtdEZpblNGMSANCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KIyMjIE5vcm1hbGl6ZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ0JzbXRGaW5TRjEnDQpzdW1tYXJ5KHZhbF90cmFpbl9YeVt4XSkNCnN1bV9hbmRfdHJhbnNfY29udCgNCiAgZGF0YSA9IHZhbF90cmFpbl9YeVt2YWxfdHJhaW5fWHkkQnNtdEZpblNGMSAhPSAwLCBdLA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSA1MCwNCiAgdF9iaW53ID0gMC4yNQ0KKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnY2JydChCc210RmluU0YxKScNCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKCdjYnJ0KEJzbXRGaW5TRjEpJyA9IEJzbXRGaW5TRjFeKDEvMykpDQoNCiMgUmVjYWxjdWxhdGUgYmVzdCBub3JtYWxpemVycy4NCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCmJlc3Rfbm9ybWFsaXplcnMgPSBmaW5kX2Jlc3Rfbm9ybWFsaXplcl9wZXJfZmVhdCgNCiAgZGYgPSB2YWxfdHJhaW5fWHksDQogIGZlYXRzX2xzdCA9IG51bV9mZWF0cywNCiAgZnVuY3NfbHN0ID0gZnVuY3NfbHN0LA0KICBleGNsdWRlX3ZhbHMgPSBsaXN0KDApDQopDQoNCnN1bW1hcnkodmFsX3RyYWluX1h5W3hdKQ0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5W3ZhbF90cmFpbl9YeSRCc210RmluU0YxICE9IDAsIF0sDQogIHggPSB4LA0KICBmdW5jID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRmdW5jLA0KICBmdW5jX25hbWUgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJG5hbWUsDQogIHhfYmludyA9IDAuMjUsDQogIHRfYmludyA9IDAuMjUNCikNCmBgYA0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IC5kYXRhW1snY2JydChCc210RmluU0YxKSddXSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oKQ0KYGBgDQoNCiMjIyBDb3JyZWxhdGlvbnMNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeF9sc3QgPSBjKCdCc210RmluU0YxJywgJ2NicnQoQnNtdEZpblNGMSknKQ0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAhaXMubmEoLmRhdGFbW3hdXSkNCiAgKSwNCiAgeF9sc3QgPSB4X2xzdCwNCiAgZmVhdHMgPSBudW1fZmVhdHMNCikNCmRmDQpwcmludCgiU3VtbWFyeSBvZiBhYnNvbHV0ZSB2YWx1ZXMgb2YgUGVhcnNvbidzIFJzOiIpDQpkZiA9IGFicyhkZikNCnN1bW1hcnkoYWJzKGRmKSkNCg0KZGYgPSBtZWx0KGRmKQ0KZ2dwbG90KGRmLCBhZXMoeCA9IHZhcmlhYmxlLCB5ID0gdmFsdWUpKSArDQogIGdlb21fYm94cGxvdChub3RjaCA9IFQpICsNCiAgeWxhYihsYWJlbCA9ICdBYnNvbHV0ZSBWYWx1ZSBvZiBDb3JyZWxhdGlvbiB0byBPdGhlciBGZWF0dXJlcycpDQoNCnlfbHN0ID0gYygnbG9nKFNhbGVQcmljZSknKQ0KcGxvdF9zY2F0X3BhaXJzKGRmID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeV9sc3QgPSB5X2xzdCkNCnBsb3Rfc2NhdF9wYWlycyhkZiA9IHZhbF90cmFpbl9YeSwgeCA9ICdCc210RmluU0YxJywgeV9sc3QgPSB5X2xzdCkNCmBgYA0KDQpUaGlzIHdpbGwgYmUgZHJvcHBlZCBpbiBmYXZvciBvZiBhIGRlcml2YXRpdmUgdmFyaWFibGUsIHRvdGFsIGJhc2VtZW50IHNmLCBzbyBubyBuZWVkIHRvIGdvIGFueSBmdXJ0aGVyLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp2YWxfdHJhaW5fWHkgPSBzZWxlY3QodmFsX3RyYWluX1h5LCAtYygnY2JydChCc210RmluU0YxKScpKQ0KYGBgDQoNCiMjIEJzbXRGaW5UeXBlMg0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpNb3N0bHkgdW5maW5pc2hlZCAoNjA5KSBidXQgMjcgbm8gYmFzZW1lbnRzLiBTbywgb25seSBvbmUgaG91c2UgaW4gQW1lcyB3aXRoIGEgYmFzZW1lbnQgZG9lc24ndCBoYXZlIGEgc2Vjb25kIGJhc2VtZW50Pz8gVGhpcyBkb2Vzbid0IHNlZW0gcmlnaHQuIEl0IG1pZ2h0IGJlIHdvcnRoIGRyb3BwaW5nIHRoaXMgZmVhdHVyZS4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdCc210RmluVHlwZTInDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5LCBtaW5fbiA9IDI5KQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQpgYGANCg0KIyMgQnNtdEZpblNGMiANCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KTm8gbmVlZCB0byBsb29rIGZ1cnRoZXIgYXMgdGhpcyB3aWxsIGJlIGRyb3BwZWQgaW4gZmF2b3Igb2YgdG90YWwgYnNtdCBzZi4NCg0KIyMgQnNtdFVuZlNGIA0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQojIyMgTm9ybWFsaXplDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnQnNtdFVuZlNGJw0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbeF0pDQpzdW1fYW5kX3RyYW5zX2NvbnQoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHksDQogIHggPSB4LA0KICBmdW5jID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRmdW5jLA0KICBmdW5jX25hbWUgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJG5hbWUsDQogIHhfYmludyA9IDUwLA0KICB0X2JpbncgPSAwLjI1DQopDQpgYGANCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdjYnJ0KEJzbXRVbmZTRiknDQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgnY2JydChCc210VW5mU0YpJyA9IEJzbXRVbmZTRl4oMS8zKSkNCg0KIyBSZWNhbGN1bGF0ZSBiZXN0IG5vcm1hbGl6ZXJzLg0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KYmVzdF9ub3JtYWxpemVycyA9IGZpbmRfYmVzdF9ub3JtYWxpemVyX3Blcl9mZWF0KA0KICBkZiA9IHZhbF90cmFpbl9YeSwNCiAgZmVhdHNfbHN0ID0gbnVtX2ZlYXRzLA0KICBmdW5jc19sc3QgPSBmdW5jc19sc3QsDQogIGV4Y2x1ZGVfdmFscyA9IGxpc3QoMCkNCikNCg0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbeF0pDQpzdW1fYW5kX3RyYW5zX2NvbnQoDQogIGRhdGEgPSBmaWx0ZXIodmFsX3RyYWluX1h5LCBCc210VW5mU0YgIT0gMCksDQogIHggPSB4LA0KICBmdW5jID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRmdW5jLA0KICBmdW5jX25hbWUgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJG5hbWUsDQogIHhfYmludyA9IDAuMjUsDQogIHRfYmludyA9IDAuMjUNCikNCmBgYA0KDQojIyMgV2luc29yaXplDQoNCkJlY2F1c2UgdGhpcyB2YXJpYWJsZSdzIDBzIGluZGljYXRlIGEgbWlzc2luZyBiYXNlbWVudCwganVzdCBXaW5zb3JpemluZyBub24temVybyB2YWx1ZXMuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmRmID0gdmFsX3RyYWluX1h5W3ZhbF90cmFpbl9YeSRCc210VW5mU0YgIT0gMCwgXQ0KDQpxcW5vcm0oeSA9IGRmJEJzbXRVbmZTRiwgeWxhYiA9ICdCc210VW5mU0YnKQ0KcXFsaW5lKHkgPSBkZiRCc210VW5mU0YsIHlsYWIgPSAnQnNtdFVuZlNGJykNCg0KcXFub3JtKHkgPSBkZltbeF1dLCB5bGFiID0geCkNCnFxbGluZSh5ID0gZGZbW3hdXSwgeWxhYiA9IHgpDQoNCldpbl9jYnJ0X3ggPSBXaW5zb3JpemUoDQogIHggPSBkZltbeF1dLA0KICBwcm9icyA9IGMoMC4wNSwgMC45NSksDQogIG5hLnJtID0gVA0KKQ0KDQpxcW5vcm0oeSA9IFdpbl9jYnJ0X3gsIHlsYWIgPSAnV2luKGNicnQoQnNtdFVuZlNGKScpDQpxcWxpbmUoeSA9IFdpbl9jYnJ0X3gsIHlsYWIgPSAnV2luKGNicnQoQnNtdFVuZlNGKScpDQoNCldpbl9yYXdfeCA9IFdpbnNvcml6ZSgNCiAgeCA9IGRmJEJzbXRVbmZTRiwNCiAgcHJvYnMgPSBjKDAsIDAuOTUpLA0KICBuYS5ybSA9IFQNCikNCg0KcXFub3JtKHkgPSBXaW5fcmF3X3gsIHlsYWIgPSAnQnNtdFVuZlNGJykNCnFxbGluZSh5ID0gV2luX3Jhd194LCB5bGFiID0gJ0JzbXRVbmZTRicpDQoNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gZGYkQnNtdFVuZlNGKSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gZGYkYGNicnQoQnNtdFVuZlNGKWApKQ0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBXaW5fY2JydF94KSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gV2luX3Jhd194KSkNCmBgYA0KDQojIyMgQ29ycmVsYXRpb25zDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnY2JydChCc210VW5mU0YpJw0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeF9sc3QgPSBjKCdCc210VW5mU0YnLCAnY2JydChCc210VW5mU0YpJykNCg0KZGYgPSBnZXRfY29ycygNCiAgZGF0YSA9IGZpbHRlcigNCiAgICBzZWxlY3QodmFsX3RyYWluX1h5LCBhbGxfb2YobnVtX2ZlYXRzKSksDQogICAgIWlzLm5hKC5kYXRhW1t4XV0pDQogICksDQogIHhfbHN0ID0geF9sc3QsDQogIGZlYXRzID0gbnVtX2ZlYXRzDQopDQpkZg0KcHJpbnQoIlN1bW1hcnkgb2YgYWJzb2x1dGUgdmFsdWVzIG9mIFBlYXJzb24ncyBSczoiKQ0KZGYgPSBhYnMoZGYpDQpzdW1tYXJ5KGFicyhkZikpDQoNCmRmID0gbWVsdChkZikNCmdncGxvdChkZiwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHZhbHVlKSkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArDQogIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMnKQ0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAuZGF0YVtbeF1dICE9IDANCiAgKSwNCiAgeF9sc3QgPSB4X2xzdCwNCiAgZmVhdHMgPSBudW1fZmVhdHMNCikNCmRmDQpwcmludCgiU3VtbWFyeSBvZiBhYnNvbHV0ZSB2YWx1ZXMgb2YgUGVhcnNvbidzIFJzIChubyAwcyk6IikNCmRmID0gYWJzKGRmKQ0Kc3VtbWFyeShhYnMoZGYpKQ0KDQpkZiA9IG1lbHQoZGYpDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gdmFyaWFibGUsIHkgPSB2YWx1ZSkpICsNCiAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKw0KICB5bGFiKGxhYmVsID0gJ0Fic29sdXRlIFZhbHVlIG9mIENvcnJlbGF0aW9uIHRvIE90aGVyIEZlYXR1cmVzIChubyAwcyknKQ0KDQp5X2xzdCA9IGMoJ2xvZyhTYWxlUHJpY2UpJykNCmZvciAoZmVhdCBpbiB4X2xzdCkgew0KICBwbG90X3NjYXRfcGFpcnMoZGYgPSB2YWxfdHJhaW5fWHksIHggPSBmZWF0LCB5X2xzdCA9IHlfbHN0KQ0KfQ0KYGBgDQoNCiMjIFRvdGFsQnNtdFNGIA0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQojIyMgTm9ybWFsaXplDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnVG90YWxCc210U0YnDQpzdW1tYXJ5KHZhbF90cmFpbl9YeVt4XSkNCnN1bV9hbmRfdHJhbnNfY29udCgNCiAgZGF0YSA9IHZhbF90cmFpbl9YeSwNCiAgeCA9IHgsDQogIGZ1bmMgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJGZ1bmMsDQogIGZ1bmNfbmFtZSA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkbmFtZSwNCiAgeF9iaW53ID0gNTAsDQogIHRfYmludyA9IDEvMjANCikNCmBgYA0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ2xvZyhUb3RhbEJzbXRTRiknDQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgNCiAgICAnbG9nKFRvdGFsQnNtdFNGKScgPSBpZmVsc2UoDQogICAgVG90YWxCc210U0YgPD0gMCwNCiAgICAwLA0KICAgIGxvZyhUb3RhbEJzbXRTRikNCiAgICApDQogICkNCg0KIyBSZWNhbGN1bGF0ZSBiZXN0IG5vcm1hbGl6ZXJzLg0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KYmVzdF9ub3JtYWxpemVycyA9IGZpbmRfYmVzdF9ub3JtYWxpemVyX3Blcl9mZWF0KA0KICBkZiA9IHZhbF90cmFpbl9YeSwNCiAgZmVhdHNfbHN0ID0gbnVtX2ZlYXRzLA0KICBmdW5jc19sc3QgPSBmdW5jc19sc3QsDQogIGV4Y2x1ZGVfdmFscyA9IGxpc3QoMCkNCikNCg0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbeF0pDQpzdW1fYW5kX3RyYW5zX2NvbnQoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHksDQogIHggPSB4LA0KICBmdW5jID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRmdW5jLA0KICBmdW5jX25hbWUgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJG5hbWUsDQogIHhfYmludyA9IDEvMjAsDQogIHRfYmludyA9IDENCikNCmBgYA0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ3NxdWFyZShsb2coVG90YWxCc210U0YpKScNCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKA0KICAgICdzcXVhcmUobG9nKFRvdGFsQnNtdFNGKSknID0gaWZlbHNlKA0KICAgIFRvdGFsQnNtdFNGIDw9IDAsDQogICAgMCwNCiAgICBsb2coVG90YWxCc210U0YpXjINCiAgICApDQogICkNCg0KIyBSZWNhbGN1bGF0ZSBiZXN0IG5vcm1hbGl6ZXJzLg0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KYmVzdF9ub3JtYWxpemVycyA9IGZpbmRfYmVzdF9ub3JtYWxpemVyX3Blcl9mZWF0KA0KICBkZiA9IHZhbF90cmFpbl9YeSwNCiAgZmVhdHNfbHN0ID0gbnVtX2ZlYXRzLA0KICBmdW5jc19sc3QgPSBmdW5jc19sc3QsDQogIGV4Y2x1ZGVfdmFscyA9IGxpc3QoMCkNCikNCg0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbeF0pDQpzdW1fYW5kX3RyYW5zX2NvbnQoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHksDQogIHggPSB4LA0KICBmdW5jID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRmdW5jLA0KICBmdW5jX25hbWUgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJG5hbWUsDQogIHhfYmludyA9IDEsDQogIHRfYmludyA9IDENCikNCmBgYA0KDQojIyMgV2luc29yaXplDQoNCkp1c3QgY2hlY2tpbmcgbm9uLXplcm8gc2V0Lg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpkZiA9IHZhbF90cmFpbl9YeVt2YWxfdHJhaW5fWHkkVG90YWxCc210U0YgIT0gMCwgXQ0KDQpxcW5vcm0oeSA9IGRmJFRvdGFsQnNtdFNGLCB5bGFiID0gJ1RvdGFsQnNtdFNGJykNCnFxbGluZSh5ID0gZGYkVG90YWxCc210U0YsIHlsYWIgPSAnVG90YWxCc210U0YnKQ0KDQpxcW5vcm0oeSA9IGRmW1t4XV0sIHlsYWIgPSB4KQ0KcXFsaW5lKHkgPSBkZltbeF1dLCB5bGFiID0geCkNCg0KV2luX2xvZ194X3NxdWFyZWQgPSBXaW5zb3JpemUoDQogIHggPSBkZltbeF1dLA0KICBwcm9icyA9IGMoMC4wMDUsIDAuOTk1KSwNCiAgbmEucm0gPSBUDQopDQoNCnFxbm9ybSh5ID0gV2luX2xvZ194X3NxdWFyZWQsIHlsYWIgPSAnV2luX2xvZ194X3NxdWFyZWQnKQ0KcXFsaW5lKHkgPSBXaW5fbG9nX3hfc3F1YXJlZCwgeWxhYiA9ICdXaW5fbG9nX3hfc3F1YXJlZCcpDQoNCldpbl9yYXdfeCA9IFdpbnNvcml6ZSgNCiAgeCA9IGRmJFRvdGFsQnNtdFNGLA0KICBwcm9icyA9IGMoMCwgMC45OSksDQogIG5hLnJtID0gVA0KKQ0KDQpxcW5vcm0oeSA9IFdpbl9yYXdfeCwgeWxhYiA9ICdXaW5fcmF3X3gnKQ0KcXFsaW5lKHkgPSBXaW5fcmF3X3gsIHlsYWIgPSAnV2luX3Jhd194JykNCg0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBkZiRUb3RhbEJzbXRTRikpDQpwcmludChzaGFwaXJvLnRlc3QoeD0gZGYkYHNxdWFyZShsb2coVG90YWxCc210U0YpKWApKQ0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBXaW5fbG9nX3hfc3F1YXJlZCkpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IFdpbl9yYXdfeCkpDQpgYGANCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdXaW4oc3F1YXJlKGxvZyhUb3RhbEJzbXRTRikpKScNCg0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoDQogICAgJ1dpbihzcXVhcmUobG9nKFRvdGFsQnNtdFNGKSkpJyA9IGlmZWxzZSgNCiAgICAgIFRvdGFsQnNtdFNGIDw9IDAsDQogICAgICAwLA0KICAgICAgV2luc29yaXplKA0KICAgICAgICB4ID0gbG9nKFRvdGFsQnNtdFNGKV4yLA0KICAgICAgICBwcm9icyA9IGMoMC4wMDUsIDAuOTk1KSwNCiAgICAgICAgbmEucm0gPSBUDQogICAgICApDQogICAgKQ0KICApICU+JQ0KICBtdXRhdGUoDQogICAgJ1dpbihsb2coVG90YWxCc210U0YpKScgPSBpZmVsc2UoDQogICAgICBUb3RhbEJzbXRTRiA8PSAwLA0KICAgICAgMCwNCiAgICAgIFdpbnNvcml6ZSgNCiAgICAgICAgeCA9IGxvZyhUb3RhbEJzbXRTRiksDQogICAgICAgIHByb2JzID0gYygwLjAwNSwgMC45OTUpLA0KICAgICAgICBuYS5ybSA9IFQNCiAgICAgICkNCiAgICApDQogICkNCmBgYA0KDQojIyMgQ29ycmVsYXRpb25zDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCnhfbHN0ID0gYygnVG90YWxCc210U0YnLCAnbG9nKFRvdGFsQnNtdFNGKScsICdzcXVhcmUobG9nKFRvdGFsQnNtdFNGKSknLA0KICAgICAgICAgICdXaW4oc3F1YXJlKGxvZyhUb3RhbEJzbXRTRikpKScpDQoNCmRmID0gZ2V0X2NvcnMoDQogIGRhdGEgPSBmaWx0ZXIoDQogICAgc2VsZWN0KHZhbF90cmFpbl9YeSwgYWxsX29mKG51bV9mZWF0cykpLA0KICAgICFpcy5uYSguZGF0YVtbeF1dKQ0KICApLA0KICB4X2xzdCA9IHhfbHN0LA0KICBmZWF0cyA9IG51bV9mZWF0cw0KKQ0KZGYNCnByaW50KCJTdW1tYXJ5IG9mIGFic29sdXRlIHZhbHVlcyBvZiBQZWFyc29uJ3MgUnM6IikNCmRmID0gYWJzKGRmKQ0Kc3VtbWFyeShhYnMoZGYpKQ0KDQpkZiA9IG1lbHQoZGYpDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gdmFyaWFibGUsIHkgPSB2YWx1ZSkpICsNCiAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKw0KICB5bGFiKGxhYmVsID0gJ0Fic29sdXRlIFZhbHVlIG9mIENvcnJlbGF0aW9uIHRvIE90aGVyIEZlYXR1cmVzJykNCg0KZGYgPSBnZXRfY29ycygNCiAgZGF0YSA9IGZpbHRlcigNCiAgICBzZWxlY3QodmFsX3RyYWluX1h5LCBhbGxfb2YobnVtX2ZlYXRzKSksDQogICAgLmRhdGFbW3hdXSAhPSAwDQogICksDQogIHhfbHN0ID0geF9sc3QsDQogIGZlYXRzID0gbnVtX2ZlYXRzDQopDQpkZg0KcHJpbnQoIlN1bW1hcnkgb2YgYWJzb2x1dGUgdmFsdWVzIG9mIFBlYXJzb24ncyBScyAobm8gMHMpOiIpDQpkZiA9IGFicyhkZikNCnN1bW1hcnkoYWJzKGRmKSkNCg0KZGYgPSBtZWx0KGRmKQ0KZ2dwbG90KGRmLCBhZXMoeCA9IHZhcmlhYmxlLCB5ID0gdmFsdWUpKSArDQogIGdlb21fYm94cGxvdChub3RjaCA9IFQpICsNCiAgeWxhYihsYWJlbCA9ICdBYnNvbHV0ZSBWYWx1ZSBvZiBDb3JyZWxhdGlvbiB0byBPdGhlciBGZWF0dXJlcycpICsNCiAgeGxhYihsYWJlbCA9ICdTdWJzZXQgd2l0aCBubyAwcycpDQoNCnlfbHN0ID0gYygnbG9nKFNhbGVQcmljZSknKQ0KZm9yIChmZWF0IGluIHhfbHN0KSB7DQogIHBsb3Rfc2NhdF9wYWlycyhkZiA9IHZhbF90cmFpbl9YeSwgeCA9IGZlYXQsIHlfbHN0ID0geV9sc3QpDQp9DQpgYGANCg0KTG9va3MgdG8gYmUgc29tZSBjbHVzdGVyaW5nIGhlcmUsIHR3byBvciB0aHJlZSBncm91cHMuIE1heWJlIHRob3NlIHdpdGggYW5kIHdpdGhvdXQgYSBzZWNvbmQgYmFzZW1lbnQsIG1heWJlIGJhc2VtZW50IHR5cGVzLiBJIHRoaW5rIHJlZ3Jlc3Npb24gd2lsbCBzdXNzIG91dCB3aGF0IGluZm9ybWF0aW9uIGlzIG5lZWRlZCwgYnV0IGl0J3Mgd29ydGggbm90aW5nLg0KDQpUaGUgcmF3IGZlYXR1cmUgYWN0dWFsbHkgY29ycmVsYXRlcyBtdWNoIGJldHRlciB3aXRoIHRoZSB0YXJnZXQgdmFyaWFibGUgd2hlbiAwcyBhcmUgaW5jbHVkZWQuIEl0IG1pZ2h0IGJlIHdvcnRoIGtlZXBpbmcgdGhlIHJhdyBmZWF0dXJlcyBmb3IgdGhlIExhc3NvIHJlZ3Jlc3Npb24gdG8gd2VlZCB0aHJvdWdoLiBOb3JtYWxpemluZyBhcGFydCBmcm9tIDBzIG1heSBvciBtYXkgbm90IGhlbHAgb3RoZXIgcmVncmVzc2lvbnM7IGl0IHdvdWxkIGJlIGlkZWFsIGZvciBzb21lIGtpbmQgb2YgY29tYm8gcmVncmVzc2lvbiB0aGF0IHNwbGl0cy9jbHVzdGVycyB0aGVuIGZpbmRzIGEgbGluZWFyIHJlZ3Jlc3Npb24sIHdoaWNoIGlzIHNvcnQgb2Ygd2hhdCBJJ20gYXR0ZW1wdGluZyBieSBmZWVkaW5nIHRoZSBiaW5hcnkgdmVyc2lvbiBvZiB0aGVzZSBmZWF0dXJlcyB0byBMYXNzbyAod2hlcmUgbm90IGFscmVhZHkgZW5jb2RlZCBieSBhIGZhY3RvcikuIE5vcm1hbGl6aW5nIGFwYXJ0IGZvciAwcyBpcyBub3QgbGlrZWx5IHRvIGhlbHAgZGVjaXNpb24gdHJlZXMgbXVjaCwgYnV0IG1heSBoZWxwIEtOTiBhIGxpdHRsZSBieSBwdWxsaW5nIDBzIGZhcnRoZXIgYXdheSBhbmQgb3V0bGllcnMgY2xvc2VyLg0KDQojIyMgSGFyZCBDb2RlDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnV2luKHNxdWFyZShsb2coVG90YWxCc210U0YpKSknDQoNCm1pbl92YWwgPSBtaW4oV2luX2xvZ194X3NxdWFyZWQpDQptYXhfdmFsID0gbWF4KFdpbl9sb2dfeF9zcXVhcmVkKQ0KcHJpbnQocGFzdGUoIm1pbl92YWw6IiwgbWluX3ZhbCkpDQpwcmludChwYXN0ZSgibWF4X3ZhbDoiLCBtYXhfdmFsKSkNCg0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoDQogICAgJ1dpbihzcXVhcmUobG9nKFRvdGFsQnNtdFNGKSkpJyA9IGlmZWxzZSgNCiAgICAgIFRvdGFsQnNtdFNGID09IDAsDQogICAgICAwLA0KICAgICAgV2luc29yaXplKA0KICAgICAgICBsb2coVG90YWxCc210U0YpXjIsDQogICAgICAgICMgcHJvYnMgPSBjKDAuMDA1LCAwLjk5NSksDQogICAgICAgIG1pbnZhbCA9IG1pbl92YWwsDQogICAgICAgIG1heHZhbCA9IG1heF92YWwNCiAgICAgICkNCiAgICApDQogICkgJT4lDQogIHNlbGVjdCgtYygnV2luKGxvZyhUb3RhbEJzbXRTRikpJywgJ2xvZyhUb3RhbEJzbXRTRiknKSkNCg0KZ2cgPSBnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IC5kYXRhW1t4XV0pKQ0KcDEgPSBnZyArIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSkNCnAyID0gZ2cgKyBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKQ0KZ3JpZC5hcnJhbmdlKHAxLCBwMikNCmBgYA0KDQojIyMgQmluYXJpemUNCg0KSSBjYW4gY3JlYXRlIGEgYmluYXJ5IGZvciBiYXNlbWVudCB3aGVuIEkgb25lLWhvdCBlbmNvZGUgZHVyaW5nIG1vZGVsaW5nLCBidXQgSSB3YW50IHRvIGJlIGFibGUgdG8gdXNlIGl0IGZvciBleHBsb3JhdGlvbiBoZXJlLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgnQnNtdC5iaW4nID0gZmFjdG9yKGlmZWxzZShUb3RhbEJzbXRTRiA9PSAwLCAwLCAxKSwgb3JkZXJlZCA9IFQpKQ0KDQp4ID0gJ0JzbXQuYmluJw0KeSA9ICdTYWxlUHJpY2UnDQpzdW1tYXJpemVfYnkoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KeSA9ICdsb2coU2FsZVByaWNlKScNCnN1bV9hbmRfdHJhbnNfZmFjdChkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB6ID0geSwgbWluX24gPSAyMCkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXkgPSBGDQogICkNCg0KcHJpbnQoDQogICAgcGFzdGUoDQogICAgICAiTGV2ZWxzIHcvIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IiwNCiAgICAgIHksDQogICAgICAidGhhbiBhbm90aGVyIGxldmVsOiINCiAgICApDQogICkNCnByaW50KHBfdmFscyRzaWduaWZfbGV2ZWxzKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHNlbGVjdCh2YWxfdHJhaW5fWHksIC1jKCdCc210LmJpbicpKQ0KYGBgDQoNCiMjIFRvdGFsQnNtdEZpblNGIA0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpUb3RhbEJzbXRTRiAtIEJzbXRVbmZTRiANCg0KIyMjIE5vcm1hbGl6ZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ1RvdGFsQnNtdEZpblNGJw0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoJ1RvdGFsQnNtdEZpblNGJyA9IFRvdGFsQnNtdFNGIC0gQnNtdFVuZlNGKQ0KDQojIFJlY2FsY3VsYXRlIGJlc3Qgbm9ybWFsaXplcnMuDQpudW1fZmVhdHMgPSBjb2xuYW1lcyhzZWxlY3QodmFsX3RyYWluX1h5LCB3aGVyZShpcy5udW1lcmljKSkpDQpiZXN0X25vcm1hbGl6ZXJzID0gZmluZF9iZXN0X25vcm1hbGl6ZXJfcGVyX2ZlYXQoDQogIGRmID0gdmFsX3RyYWluX1h5LA0KICBmZWF0c19sc3QgPSBudW1fZmVhdHMsDQogIGZ1bmNzX2xzdCA9IGZ1bmNzX2xzdCwNCiAgZXhjbHVkZV92YWxzID0gbGlzdCgwKQ0KKQ0KDQpzdW1tYXJ5KHZhbF90cmFpbl9YeVt4XSkNCg0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5W3ZhbF90cmFpbl9YeVtbeF1dICE9IDAsIF0sDQogIHggPSB4LA0KICBmdW5jID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRmdW5jLA0KICBmdW5jX25hbWUgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJG5hbWUsDQogIHhfYmludyA9IDUwLA0KICB0X2JpbncgPSAxDQopDQpgYGANCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdzcXJ0KFRvdGFsQnNtdEZpblNGKScNCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKCdzcXJ0KFRvdGFsQnNtdEZpblNGKScgPSBzcXJ0KFRvdGFsQnNtdEZpblNGKSkNCg0KIyBSZWNhbGN1bGF0ZSBiZXN0IG5vcm1hbGl6ZXJzLg0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KYmVzdF9ub3JtYWxpemVycyA9IGZpbmRfYmVzdF9ub3JtYWxpemVyX3Blcl9mZWF0KA0KICBkZiA9IHZhbF90cmFpbl9YeSwNCiAgZmVhdHNfbHN0ID0gbnVtX2ZlYXRzLA0KICBmdW5jc19sc3QgPSBmdW5jc19sc3QsDQogIGV4Y2x1ZGVfdmFscyA9IGxpc3QoMCkNCikNCg0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbeF0pDQoNCnN1bV9hbmRfdHJhbnNfY29udCgNCiAgZGF0YSA9IHZhbF90cmFpbl9YeVt2YWxfdHJhaW5fWHlbW3hdXSAhPSAwLCBdLA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAxLA0KICB0X2JpbncgPSAxDQopDQpgYGANCg0KIyMjIFdpbnNvcml6ZQ0KDQpKdXN0IHRoZSBub24temVybyBzZXQuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnc3FydChUb3RhbEJzbXRGaW5TRiknDQpkZiA9IHZhbF90cmFpbl9YeVt2YWxfdHJhaW5fWHkkVG90YWxCc210RmluU0YgIT0gMCwgXQ0KDQpxcW5vcm0oeSA9IGRmJFRvdGFsQnNtdEZpblNGLCB5bGFiID0gJ1RvdGFsQnNtdEZpblNGJykNCnFxbGluZSh5ID0gZGYkVG90YWxCc210RmluU0YsIHlsYWIgPSAnVG90YWxCc210RmluU0YnKQ0KDQpxcW5vcm0oeSA9IGRmW1t4XV0sIHlsYWIgPSB4KQ0KcXFsaW5lKHkgPSBkZltbeF1dLCB5bGFiID0geCkNCg0KDQpXaW5fc3FydF94ID0gV2luc29yaXplKA0KICB4ID0gZGZbW3hdXSwNCiAgcHJvYnMgPSBjKDAuMDMsIDAuOTk1KSwNCiAgbmEucm0gPSBUDQopDQoNCnFxbm9ybSh5ID0gV2luX3NxcnRfeCwgeWxhYiA9ICdXaW5fc3FydF94JykNCnFxbGluZSh5ID0gV2luX3NxcnRfeCwgeWxhYiA9ICdXaW5fc3FydF94JykNCg0KDQpXaW5fcmF3X3ggPSBXaW5zb3JpemUoDQogIHggPSBkZiRUb3RhbEJzbXRGaW5TRiwNCiAgcHJvYnMgPSBjKDAuMDAxLCAwLjk5KSwNCiAgbmEucm0gPSBUDQopDQoNCnFxbm9ybSh5ID0gV2luX3Jhd194LCB5bGFiID0gJ1dpbihUb3RhbEJzbXRGaW5TRiknKQ0KcXFsaW5lKHkgPSBXaW5fcmF3X3gsIHlsYWIgPSAnV2luKFRvdGFsQnNtdEZpblNGKScpDQoNCg0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBkZiRUb3RhbEJzbXRGaW5TRikpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IGRmJGBzcXJ0KFRvdGFsQnNtdEZpblNGKWApKQ0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBXaW5fc3FydF94KSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gV2luX3Jhd194KSkNCmBgYA0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgNCiAgICAnV2luKHNxcnQoVG90YWxCc210RmluU0YpKScgPSBpZmVsc2UoDQogICAgICBUb3RhbEJzbXRGaW5TRiA9PSAwLA0KICAgICAgMCwNCiAgICAgIFdpbnNvcml6ZSgNCiAgICAgICAgeCA9IHNxcnQoVG90YWxCc210RmluU0YpLA0KICAgICAgICAjIHByb2JzID0gYygwLjAxLCAwLjk5NSksDQogICAgICAgIG1pbnZhbCA9IG1pbihXaW5fc3FydF94KSwNCiAgICAgICAgbWF4dmFsID0gbWF4KFdpbl9zcXJ0X3gpDQogICAgICApDQogICAgKQ0KICApICU+JQ0KICBtdXRhdGUoDQogICAgJ1dpbihUb3RhbEJzbXRGaW5TRiknID0gaWZlbHNlKA0KICAgICAgVG90YWxCc210RmluU0YgPT0gMCwNCiAgICAgIDAsDQogICAgICBXaW5zb3JpemUoDQogICAgICAgIHggPSBUb3RhbEJzbXRGaW5TRiwNCiAgICAgICAgIyBwcm9icyA9IGMoMC4wMDEsIDAuOTkpDQogICAgICAgIG1pbnZhbCA9IG1pbihXaW5fcmF3X3gpLA0KICAgICAgICBtYXh2YWwgPSBtYXgoV2luX3Jhd194KQ0KICAgICAgKQ0KICAgICkNCiAgKQ0KDQp4ID0gJ1dpbihzcXJ0KFRvdGFsQnNtdEZpblNGKSknDQpgYGANCg0KIyMjIENvcnJlbGF0aW9ucw0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpudW1fZmVhdHMgPSBjb2xuYW1lcyhzZWxlY3QodmFsX3RyYWluX1h5LCB3aGVyZShpcy5udW1lcmljKSkpDQp4X2xzdCA9IGMoJ1RvdGFsQnNtdEZpblNGJywgJ3NxcnQoVG90YWxCc210RmluU0YpJywgeCkNCg0KZGYgPSBnZXRfY29ycygNCiAgZGF0YSA9IGZpbHRlcigNCiAgICBzZWxlY3QodmFsX3RyYWluX1h5LCBhbGxfb2YobnVtX2ZlYXRzKSksDQogICAgIWlzLm5hKC5kYXRhW1t4XV0pDQogICksDQogIHhfbHN0ID0geF9sc3QsDQogIGZlYXRzID0gbnVtX2ZlYXRzDQopDQpkZg0KcHJpbnQoIlN1bW1hcnkgb2YgYWJzb2x1dGUgdmFsdWVzIG9mIFBlYXJzb24ncyBSczoiKQ0KZGYgPSBhYnMoZGYpDQpzdW1tYXJ5KGFicyhkZikpDQoNCmRmID0gbWVsdChkZikNCmdncGxvdChkZiwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHZhbHVlKSkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArDQogIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMnKQ0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAuZGF0YVtbeF1dICE9IDANCiAgKSwNCiAgeF9sc3QgPSB4X2xzdCwNCiAgZmVhdHMgPSBudW1fZmVhdHMNCikNCmRmDQpwcmludCgiU3VtbWFyeSBvZiBhYnNvbHV0ZSB2YWx1ZXMgb2YgUGVhcnNvbidzIFJzIChubyAwcyk6IikNCmRmID0gYWJzKGRmKQ0Kc3VtbWFyeShhYnMoZGYpKQ0KDQpkZiA9IG1lbHQoZGYpDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gdmFyaWFibGUsIHkgPSB2YWx1ZSkpICsNCiAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKw0KICB5bGFiKGxhYmVsID0gJ0Fic29sdXRlIFZhbHVlIG9mIENvcnJlbGF0aW9uIHRvIE90aGVyIEZlYXR1cmVzIChubyAwcyknKQ0KDQp5X2xzdCA9IGMoJ2xvZyhTYWxlUHJpY2UpJykNCmZvciAoZmVhdCBpbiB4X2xzdCkgew0KICBwbG90X3NjYXRfcGFpcnMoZGYgPSB2YWxfdHJhaW5fWHksIHggPSBmZWF0LCB5X2xzdCA9IHlfbHN0KQ0KfQ0KYGBgDQoNCiMjIyBIYXJkIENvZGUNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdXaW4oc3FydChUb3RhbEJzbXRGaW5TRikpJw0KDQptaW5fdmFsID0gbWluKFdpbl9zcXJ0X3gpDQptYXhfdmFsID0gbWF4KFdpbl9zcXJ0X3gpDQpwcmludChwYXN0ZSgibWluX3ZhbDoiLCBtaW5fdmFsKSkNCnByaW50KHBhc3RlKCJtYXhfdmFsOiIsIG1heF92YWwpKQ0KDQojIEFscmVhZHkgaGFyZCBjb2RlZCBhYm92ZS4NCnZhbF90cmFpbl9YeSA9IHNlbGVjdCh2YWxfdHJhaW5fWHksIC1jKCdXaW4oVG90YWxCc210RmluU0YpJykpDQoNCmdnID0gZ2dwbG90KHZhbF90cmFpbl9YeSwgYWVzKHggPSAuZGF0YVtbeF1dKSkNCnAxID0gZ2cgKyBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEpDQpwMiA9IGdnICsgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkNCmdyaWQuYXJyYW5nZShwMSwgcDIpDQpgYGANCg0KIyMgSGVhdGluZw0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpNb3N0bHkgZ2FzIGZvcmNlZCBhaXIgKEdhc0EsIDY5NykuIENhbiBwcm9iYWJseSBkcm9wIHRoaXMuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnN1bW1hcnkodmFsX3RyYWluX1h5JEhlYXRpbmcpDQp2YWxfdHJhaW5fWHkgPSBzZWxlY3QodmFsX3RyYWluX1h5LCAtYygnSGVhdGluZycpKQ0KYGBgDQoNCiMjIEhlYXRpbmdRQw0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpNb3N0bHkgZXhjZWxsZW50ICgzNjApLCB0aGVuIGludGVyZXN0aW5nbHkgbW9yZSBhdmVyYWdlICgyMTEpIHRoYW4gZ29vZCAoMTE3KS4gRW5vdWdoIHZhcmlhbmNlIHRvIGtlZXAsIGJ1dCBub3Qgbm9ybWFsLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ0hlYXRpbmdRQycNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KDQpwX3ZhbHMgPSBnZXRfc2lnbmlmX2xldmVscyhkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeiA9IHksIG1pbl9uID0gMjkpDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCmBgYA0KDQojIyBDZW50cmFsQWlyDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNCjY2MiB5ZXMsIDUzIG5vLiBQcm9iYWJseSBkcm9wIHRoaXMgb25lLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzdW1tYXJ5KHZhbF90cmFpbl9YeSRDZW50cmFsQWlyKQ0KdmFsX3RyYWluX1h5ID0gc2VsZWN0KHZhbF90cmFpbl9YeSwgLWMoJ0NlbnRyYWxBaXInKSkNCmBgYA0KDQojIyBFbGVjdHJpY2FsDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNCjY1NSBzdGFuZGFyZCBjaXJjdWl0IGJyZWFrZXIgYW5kIFJvbWV4LiBQcm9iYWJseSBkcm9wLCBidXQgdGhlcmUgYXJlIG5vbmUgbWl4ZWQsIHNvIGl0IG1pZ2h0IGJlIHdvcnRoIG1ha2luZyBpdCBhbiBvcmRlcmVkIGZhY3RvciBpZiBrZWVwaW5nLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzdW1tYXJ5KHZhbF90cmFpbl9YeSRFbGVjdHJpY2FsKQ0KdmFsX3RyYWluX1h5ID0gc2VsZWN0KHZhbF90cmFpbl9YeSwgLWMoJ0VsZWN0cmljYWwnKSkNCmBgYA0KDQojIyBYMXN0RmxyU0YNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KRHJvcHBpbmcgYXMgY29tcG9uZW50IG9mIEdyTGl2QXJlYS4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc3VtbWFyeSh2YWxfdHJhaW5fWHkkWDFzdEZsclNGKQ0KdmFsX3RyYWluX1h5ID0gc2VsZWN0KHZhbF90cmFpbl9YeSwgLWMoJ1gxc3RGbHJTRicpKQ0KYGBgDQoNCiMjIFgybmRGbHJTRg0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpEcm9wcGluZyBhcyBjb21wb25lbnQgb2YgR3JMaXZBcmVhLiBUaGUgcHJlc2VuY2Ugb2YgYSBzZWNvbmQgZmxvb3IgaXMgZW5jb2RlZCBpbiBIb3VzZVN0eWxlLCBidXQgaXQncyBhbHNvIGNvbnZvbHV0ZWQgYmV0d2VlbiBhIGZldyBsZXZlbHMuIEknbGwgbWFrZSBhIGJpbmFyeSBvdXQgb2YgaXQgYW5kIGtlZXAgdGhhdC4NCg0KIyMjIEJpbmFyaXplDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKCdYMm5kRmxyLmJpbicgPSBpZmVsc2UoWDJuZEZsclNGIDw9IDAsIDAsIDEpKSAlPiUNCiAgbXV0YXRlKCdYMm5kRmxyLmJpbi5mYWN0JyA9IGZhY3RvcihYMm5kRmxyLmJpbiwgb3JkZXJlZCA9IFQpKQ0KDQp4ID0gJ1gybmRGbHIuYmluJw0KeSA9ICdTYWxlUHJpY2UnDQpzdW1tYXJpemVfYnkoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KeCA9ICdYMm5kRmxyLmJpbi5mYWN0Jw0KeSA9ICdsb2coU2FsZVByaWNlKScNCnN1bV9hbmRfdHJhbnNfZmFjdChkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB6ID0geSwgbWluX24gPSAzMCkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXkgPSBGDQogICkNCg0KcHJpbnQoDQogICAgcGFzdGUoDQogICAgICAiTGV2ZWxzIHcvIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IiwNCiAgICAgIHksDQogICAgICAidGhhbiBhbm90aGVyIGxldmVsOiINCiAgICApDQogICkNCnByaW50KHBfdmFscyRzaWduaWZfbGV2ZWxzKQ0KDQp2YWxfdHJhaW5fWHkgPSBzZWxlY3QodmFsX3RyYWluX1h5LCAtYygnWDJuZEZsci5iaW4uZmFjdCcpKQ0KYGBgDQoNCiMjIExvd1F1YWxGaW5TRg0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQo3MDIgMHMuIERyb3AgdGhpcyBmZWF0dXJlLCB0aG91Z2ggaXQgd291bGQgYmUgYSB1c2VmdWwgbW9kaWZpZXIgb2YgR3JMaXZBcmVhIGFzIGEgZGV0cmFjdGluZyBjb21wb25lbnQgb2YgaXQuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnN1bW1hcnkodmFsX3RyYWluX1h5JExvd1F1YWxGaW5TRikNCnZhbF90cmFpbl9YeSA9IHNlbGVjdCh2YWxfdHJhaW5fWHksIC1jKCdMb3dRdWFsRmluU0YnKSkNCmBgYA0KDQojIyBHckxpdkFyZWEgDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNCiMjIyBOb3JtYWxpemUNCg0KQXMgdGhpcyBpcyBwb2x5bW9kYWwgdG8gdGhlIG51bWJlciBvZiBmbG9vcnMsIEknbGwgc3RhcnQgYnkgZmFjZXRpbmcgYmVmb3JlIHRyYW5zZm9ybWluZy4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dwbG90KHZhbF90cmFpbl9YeSwgYWVzKHggPSBHckxpdkFyZWEpKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gNTApICsNCiAgZmFjZXRfd3JhcChmYWNldHMgPSB2YXJzKFgybmRGbHIuYmluKSwgbmNvbCA9IDEpDQpgYGANCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdHckxpdkFyZWEnDQpzdW1tYXJ5KHZhbF90cmFpbl9YeVt4XSkNCnN1bV9hbmRfdHJhbnNfY29udCgNCiAgZGF0YSA9IHZhbF90cmFpbl9YeSwNCiAgeCA9IHgsDQogIGZ1bmMgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJGZ1bmMsDQogIGZ1bmNfbmFtZSA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkbmFtZSwNCiAgeF9iaW53ID0gNTAsDQogIHRfYmludyA9IC4xDQopDQpgYGANCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdsb2cyKEdyTGl2QXJlYSknDQoNCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKCdsb2cyKEdyTGl2QXJlYSknID0gbG9nMihHckxpdkFyZWEpKQ0KDQpnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IC5kYXRhW1t4XV0pKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gLjEpICsNCiAgZmFjZXRfd3JhcChmYWNldHMgPSB2YXJzKFgybmRGbHIuYmluKSwgbmNvbCA9IDEpDQoNCiMgUmVjYWxjdWxhdGUgYmVzdCBub3JtYWxpemVycy4NCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCmJlc3Rfbm9ybWFsaXplcnMgPSBmaW5kX2Jlc3Rfbm9ybWFsaXplcl9wZXJfZmVhdCgNCiAgZGYgPSB2YWxfdHJhaW5fWHksDQogIGZlYXRzX2xzdCA9IG51bV9mZWF0cywNCiAgZnVuY3NfbHN0ID0gZnVuY3NfbHN0LA0KICBleGNsdWRlX3ZhbHMgPSBsaXN0KDApDQopDQoNCnN1bW1hcnkodmFsX3RyYWluX1h5W3hdKQ0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAuMSwNCiAgdF9iaW53ID0gMQ0KKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnc3F1YXJlKGxvZzIoR3JMaXZBcmVhKSknDQoNCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKCdzcXVhcmUobG9nMihHckxpdkFyZWEpKScgPSBsb2cyKEdyTGl2QXJlYSleMikNCg0KZ2dwbG90KHZhbF90cmFpbl9YeSwgYWVzKHggPSAuZGF0YVtbeF1dKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEpICsNCiAgZmFjZXRfd3JhcChmYWNldHMgPSB2YXJzKFgybmRGbHIuYmluKSwgbmNvbCA9IDEpDQoNCiMgUmVjYWxjdWxhdGUgYmVzdCBub3JtYWxpemVycy4NCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCmJlc3Rfbm9ybWFsaXplcnMgPSBmaW5kX2Jlc3Rfbm9ybWFsaXplcl9wZXJfZmVhdCgNCiAgZGYgPSB2YWxfdHJhaW5fWHksDQogIGZlYXRzX2xzdCA9IG51bV9mZWF0cywNCiAgZnVuY3NfbHN0ID0gZnVuY3NfbHN0LA0KICBleGNsdWRlX3ZhbHMgPSBsaXN0KDApDQopDQoNCnN1bW1hcnkodmFsX3RyYWluX1h5W3hdKQ0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAxLA0KICB0X2JpbncgPSAxDQopDQpgYGANCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dwbG90KHZhbF90cmFpbl9YeSwgYWVzKHggPSBgc3F1YXJlKGxvZzIoR3JMaXZBcmVhKSlgKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEpICsNCiAgZmFjZXRfd3JhcChmYWNldHMgPSB2YXJzKC5kYXRhJFgybmRGbHIuYmluKSwgbmNvbCA9IDEpDQoNCmdncGxvdCh2YWxfdHJhaW5fWHksIGFlcyh4ID0gYHNxdWFyZShsb2cyKEdyTGl2QXJlYSkpYCkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxKSArDQogIGZhY2V0X2dyaWQoDQogICAgY29scyA9IHZhcnMoLmRhdGEkWDJuZEZsci5iaW4pLA0KICAgIHJvd3MgPSB2YXJzKC5kYXRhJFllYXJCdWlsdC5mYWN0KQ0KICApDQpgYGANCg0KSXQgbG9va3MgbGlrZSB0aGVyZSBtaWdodCBiZSBtb3JlIHBvbHltb2RhbGl0eSwgcGFydGljdWxhcmx5IGFtb25nIG1pZC1jZW50dXJ5IGJ1aWxkcywgYnV0IEknbGwgbGVhdmUgaXQgaGVyZS4gVGhlIHRyYW5zZm9ybWF0aW9ucyBzZWVtIHRvIGhhdmUgYmV0dGVyIG5vcm1hbGl6ZWQgYm90aCB0aGUgZnVsbCB2YXJpYWJsZSBhbmQgaXRzIHN1YnNldHMgYnkgc3RvcmV5IGFuZCBhZ2UuIFdpbnNvcml6YXRpb24gc2hvdWxkIGhlbHAgYm90aCB0b28uDQoNCiMjIyBXaW5zb3JpemUNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdzcXVhcmUobG9nMihHckxpdkFyZWEpKScNCg0KcXFub3JtKHkgPSB2YWxfdHJhaW5fWHkkR3JMaXZBcmVhLCB5bGFiID0gJ0dyTGl2QXJlYScpDQpxcWxpbmUoeSA9IHZhbF90cmFpbl9YeSRHckxpdkFyZWEsIHlsYWIgPSAnR3JMaXZBcmVhJykNCg0KcXFub3JtKHkgPSB2YWxfdHJhaW5fWHkkYGxvZzIoR3JMaXZBcmVhKWAsIHlsYWIgPSAnbG9nMihHckxpdkFyZWEpJykNCnFxbGluZSh5ID0gdmFsX3RyYWluX1h5JGBsb2cyKEdyTGl2QXJlYSlgLCB5bGFiID0gJ2xvZzIoR3JMaXZBcmVhKScpDQoNCnFxbm9ybSh5ID0gdmFsX3RyYWluX1h5W1t4XV0sIHlsYWIgPSB4KQ0KcXFsaW5lKHkgPSB2YWxfdHJhaW5fWHlbW3hdXSwgeWxhYiA9IHgpDQoNCldpbl9sb2cyX3hfc3F1YXJlZCA9IFdpbnNvcml6ZSgNCiAgeCA9IHZhbF90cmFpbl9YeVtbeF1dLA0KICBwcm9icyA9IGMoMC4wMDIsIDAuOTk4KSwNCiAgbmEucm0gPSBUDQopDQoNCnFxbm9ybSh5ID0gV2luX2xvZzJfeF9zcXVhcmVkLCB5bGFiID0gJ1dpbl9sb2cyX3hfc3F1YXJlZCcpDQpxcWxpbmUoeSA9IFdpbl9sb2cyX3hfc3F1YXJlZCwgeWxhYiA9ICdXaW5fbG9nMl94X3NxdWFyZWQnKQ0KDQpXaW5fbG9nMl94ID0gV2luc29yaXplKA0KICB4ID0gdmFsX3RyYWluX1h5JGBsb2cyKEdyTGl2QXJlYSlgLA0KICBwcm9icyA9IGMoMC4wMDIsIDAuOTk4KSwNCiAgbmEucm0gPSBUDQopDQoNCnFxbm9ybSh5ID0gV2luX2xvZzJfeCwgeWxhYiA9ICdXaW5fbG9nMl94JykNCnFxbGluZSh5ID0gV2luX2xvZzJfeCwgeWxhYiA9ICdXaW5fbG9nMl94JykNCg0KV2luX3Jhd194ID0gV2luc29yaXplKA0KICB4ID0gdmFsX3RyYWluX1h5JEdyTGl2QXJlYSwNCiAgcHJvYnMgPSBjKDAsIDAuOTUpLA0KICBuYS5ybSA9IFQNCikNCg0KcXFub3JtKHkgPSBXaW5fcmF3X3gsIHlsYWIgPSAnV2luX3Jhd194JykNCnFxbGluZSh5ID0gV2luX3Jhd194LCB5bGFiID0gJ1dpbl9yYXdfeCcpDQoNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gdmFsX3RyYWluX1h5JEdyTGl2QXJlYSkpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IHZhbF90cmFpbl9YeSRgbG9nMihHckxpdkFyZWEpYCkpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IHZhbF90cmFpbl9YeVtbJ3NxdWFyZShsb2cyKEdyTGl2QXJlYSkpJ11dKSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gV2luX2xvZzJfeF9zcXVhcmVkKSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gV2luX2xvZzJfeCkpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IFdpbl9yYXdfeCkpDQpgYGANCg0KV2luc29yaXppbmcgdGhlIGxvZzIgYmV0dGVyIG5vcm1hbGl6ZWQgdGhhbiBzcXVhcmluZyBpdCwgYnV0IFdpbnNvcml6aW5nIHRoZSBzcXVhcmVkIGxvZzIgaXMgY2xvc2UsIHRvby4gTWF5IGJlIG92ZXJmaXQgdG8gYWRkIHRoZSBzcXVhcmUuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKA0KICAgICdXaW4obG9nMihHckxpdkFyZWEpKScgPSBXaW5zb3JpemUoDQogICAgICBpZmVsc2UoR3JMaXZBcmVhIDw9IDAsIDAsIGxvZzIoR3JMaXZBcmVhKSksDQogICAgICBwcm9icyA9IGMoMC4wMDIsIDAuOTk4KSwNCiAgICAgIG5hLnJtID0gVA0KICAgICkNCiAgKSAlPiUNCiAgbXV0YXRlKA0KICAgICdXaW4oc3F1YXJlKGxvZzIoR3JMaXZBcmVhKSkpJyA9IFdpbnNvcml6ZSgNCiAgICAgIGlmZWxzZShHckxpdkFyZWEgPD0gMCwgMCwgbG9nMihHckxpdkFyZWEpXjIpLA0KICAgICAgcHJvYnMgPSBjKDAuMDAyLCAwLjk5OCksDQogICAgICBuYS5ybSA9IFQNCiAgICApDQogICkNCmBgYA0KDQojIyMgQ29ycmVsYXRpb25zDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCnhfbHN0ID0gYygnR3JMaXZBcmVhJywgJ2xvZzIoR3JMaXZBcmVhKScsICdzcXVhcmUobG9nMihHckxpdkFyZWEpKScsICdXaW4obG9nMihHckxpdkFyZWEpKScsICdXaW4oc3F1YXJlKGxvZzIoR3JMaXZBcmVhKSkpJykNCg0KZGYgPSBnZXRfY29ycygNCiAgZGF0YSA9IGZpbHRlcigNCiAgICBzZWxlY3QodmFsX3RyYWluX1h5LCBhbGxfb2YobnVtX2ZlYXRzKSksDQogICAgIWlzLm5hKC5kYXRhW1t4XV0pDQogICksDQogIHhfbHN0ID0geF9sc3QsDQogIGZlYXRzID0gbnVtX2ZlYXRzDQopDQpkZg0KcHJpbnQoIlN1bW1hcnkgb2YgYWJzb2x1dGUgdmFsdWVzIG9mIFBlYXJzb24ncyBSczoiKQ0KZGYgPSBhYnMoZGYpDQpzdW1tYXJ5KGFicyhkZikpDQoNCmRmID0gbWVsdChkZikNCmdncGxvdChkZiwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHZhbHVlKSkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArDQogIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMnKQ0KDQp5X2xzdCA9IGMoJ2xvZyhTYWxlUHJpY2UpJykNCmZvciAoZmVhdCBpbiB4X2xzdCkgew0KICBwbG90X3NjYXRfcGFpcnMoZGYgPSB2YWxfdHJhaW5fWHksIHggPSBmZWF0LCB5X2xzdCA9IHlfbHN0KQ0KfQ0KYGBgDQoNCiMjIyBIYXJkIENvZGUNCg0KVGhlIFdpbnNvcml6ZWQgZG91YmxlIHRyYW5zZm9ybWF0aW9uIG1heSBiZSBzbGlnaHRseSBvdmVyZml0dGluZyBhbmQgb3ZlcmNvbXB1dGluZywgYnV0IGl0J3Mgc2xpZ2h0bHkgKGxpa2VseSBpbnNpZ25pZmljYW50bHkpIG1vcmUgbm9ybWFsIGFuZCBjb3JyZWxhdGVkIHRvIFNhbGVQcmljZSB0aGFuIHRoZSBXaW5zb3JpemVkIHNpbmdsZSB0cmFuc2Zvcm1hdGlvbi4gSSdsbCBnbyB3aXRoIGl0IGRlc3BpdGUgY29tbW9uIHNlbnNlLCBqdXN0IGZvciBmdW4uDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnV2luKHNxdWFyZShsb2cyKEdyTGl2QXJlYSkpKScNCg0KbWluX3ZhbCA9IG1pbihXaW5fbG9nMl94X3NxdWFyZWQpDQptYXhfdmFsID0gbWF4KFdpbl9sb2cyX3hfc3F1YXJlZCkNCnByaW50KHBhc3RlKCJtaW5fdmFsOiIsIG1pbl92YWwpKQ0KcHJpbnQocGFzdGUoIm1heF92YWw6IiwgbWF4X3ZhbCkpDQoNCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKA0KICAgICdXaW4oc3F1YXJlKGxvZzIoR3JMaXZBcmVhKSkpJyA9IFdpbnNvcml6ZSgNCiAgICAgIGlmZWxzZShHckxpdkFyZWEgPD0gMCwgMCwgbG9nMihHckxpdkFyZWEpXjIpLA0KICAgICAgIyBwcm9icyA9IGMoMC4wMDIsIDAuOTk4KSwNCiAgICAgICMgbmEucm0gPSBUDQogICAgICBtaW52YWwgPSBtaW5fdmFsLA0KICAgICAgbWF4dmFsID0gbWF4X3ZhbA0KICAgICkNCiAgKSAlPiUNCiAgc2VsZWN0KC1jKCdsb2cyKEdyTGl2QXJlYSknLCAnV2luKGxvZzIoR3JMaXZBcmVhKSknKSkNCg0KZ2cgPSBnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IC5kYXRhW1t4XV0pKQ0KcDEgPSBnZyArIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSkNCnAyID0gZ2cgKyBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKQ0KZ3JpZC5hcnJhbmdlKHAxLCBwMikNCmBgYA0KDQojIyBCc210RnVsbEJhdGgNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KVG8gY29udHJpYnV0ZSB0byBmdWxsIGNvdW50Lg0KDQojIyBCc210SGFsZkJhdGgNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KVG8gY29udHJpYnV0ZSB0byBmdWxsIGNvdW50Lg0KDQojIyBGdWxsQmF0aA0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpUbyBjb250cmlidXRlIHRvIGZ1bGwgY291bnQuDQoNCiMjIEhhbGZCYXRoDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNClRvIGNvbnRyaWJ1dGUgdG8gZnVsbCBjb3VudC4NCg0KIyMgVG90QmF0aHMNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KSSBjb3VsZCBtYWtlIHR3byBmZWF0dXJlcywgdG90YWwgYmF0aHMgYWJvdmUgZ3JhZGUgYW5kIHRvdGFsIGJhc2VtZW50IGJhdGhzLCBidXQgSSdsbCBrZWVwIGl0IHNpbXBsZS4NCg0KIyMjIE5vcm1hbGl6ZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZShUb3RCYXRocyA9IEZ1bGxCYXRoICsgQnNtdEZ1bGxCYXRoICsgMC41KkhhbGZCYXRoICsgMC41KkJzbXRIYWxmQmF0aCkNCg0KeCA9ICdUb3RCYXRocycNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KYGBgDQoNCjxhIGlkPSJ0b3RiYXRoQnNtdGJhdGgiPjwvYT4NCg0KYGBge3IgZWNobz1UUlVFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KDQpnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IGZhY3RvcihUb3RCYXRocyksIHkgPSBsb2coU2FsZVByaWNlKSkNCikgKw0KICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuNSwgYWVzKGNvbG9yID0gZmFjdG9yKEJzbXRGdWxsQmF0aCArIEJzbXRIYWxmQmF0aCkpKSArDQogIGdlb21fYm94cGxvdCgNCiAgICBub3RjaCA9IFQsDQogICAgbm90Y2h3aWR0aCA9IC4xLA0KICAgIHZhcndpZHRoID0gVCwNCiAgICBhbHBoYSA9IDAsDQogICAgY29sb3IgPSAnYmx1ZScNCiAgKSArDQogIGdlb21fdmlvbGluKGFscGhhID0gMCkgKw0KICBnZW9tX2xpbmUoDQogICAgc3RhdCA9ICdzdW1tYXJ5JywNCiAgICBmdW4gPSBxdWFudGlsZSwNCiAgICBmdW4uYXJncyA9IGxpc3QocHJvYnMgPSAuOSksDQogICAgbGluZXR5cGUgPSAyLCBhZXMoZ3JvdXAgPSAxKQ0KICApICsNCiAgZ2VvbV9saW5lKHN0YXQgPSAnc3VtbWFyeScsIGZ1biA9IG1lYW4sIG1hcHBpbmcgPSBhZXMoZ3JvdXAgPSAxKSkgKw0KICBnZW9tX2xpbmUoDQogICAgc3RhdCA9ICdzdW1tYXJ5JywNCiAgICBmdW4gPSBxdWFudGlsZSwNCiAgICBmdW4uYXJncyA9IGxpc3QocHJvYnMgPSAuMSksDQogICAgbGluZXR5cGUgPSAyLCBhZXMoZ3JvdXAgPSAxKQ0KICApICsNCiAgeGxhYihsYWJlbCA9ICdUb3RCYXRocycpICsgeWxhYihsYWJlbCA9ICdsb2coU2FsZVByaWNlKScpDQpgYGANCg0KQmFzZW1lbnQgYmF0aHJvb21zIGRvbid0IHNlZW0gdG8gYWRkIG11Y2ggdmFsdWUgd2hlbiB0aGVyZSBhcmVuJ3QgbWFueSBiYXRocm9vbXMgYWx0b2dldGhlci4NCg0KYGBge3IgZWNobz1UUlVFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5LCBtaW5fbiA9IDI5KQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQoNCiMgUmVjYWxjdWxhdGUgYmVzdCBub3JtYWxpemVycy4NCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCmJlc3Rfbm9ybWFsaXplcnMgPSBmaW5kX2Jlc3Rfbm9ybWFsaXplcl9wZXJfZmVhdCgNCiAgZGYgPSB2YWxfdHJhaW5fWHksDQogIGZlYXRzX2xzdCA9IG51bV9mZWF0cywNCiAgZnVuY3NfbHN0ID0gZnVuY3NfbHN0LA0KICBleGNsdWRlX3ZhbHMgPSBsaXN0KDApDQopDQoNCnN1bW1hcnkodmFsX3RyYWluX1h5W3hdKQ0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAwLjUsDQogIHRfYmludyA9IDAuMQ0KKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnc3FydChUb3RCYXRocyknDQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgnc3FydChUb3RCYXRocyknID0gc3FydChUb3RCYXRocykpDQoNCiMgUmVjYWxjdWxhdGUgYmVzdCBub3JtYWxpemVycy4NCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCmJlc3Rfbm9ybWFsaXplcnMgPSBmaW5kX2Jlc3Rfbm9ybWFsaXplcl9wZXJfZmVhdCgNCiAgZGYgPSB2YWxfdHJhaW5fWHksDQogIGZlYXRzX2xzdCA9IG51bV9mZWF0cywNCiAgZnVuY3NfbHN0ID0gZnVuY3NfbHN0LA0KICBleGNsdWRlX3ZhbHMgPSBsaXN0KDApDQopDQoNCnN1bW1hcnkodmFsX3RyYWluX1h5W3hdKQ0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAuMSwNCiAgdF9iaW53ID0gLjENCikNCmBgYA0KDQojIyMgV2luc29yaXplDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnc3FydChUb3RCYXRocyknDQoNCnFxbm9ybSh5ID0gdmFsX3RyYWluX1h5JFRvdEJhdGhzLCB5bGFiID0gJ1RvdEJhdGhzJykNCnFxbGluZSh5ID0gdmFsX3RyYWluX1h5JFRvdEJhdGhzLCB5bGFiID0gJ1RvdEJhdGhzJykNCg0KcXFub3JtKHkgPSB2YWxfdHJhaW5fWHlbW3hdXSwgeWxhYiA9IHgpDQpxcWxpbmUoeSA9IHZhbF90cmFpbl9YeVtbeF1dLCB5bGFiID0geCkNCg0KV2luX3NxcnRfeCA9IFdpbnNvcml6ZSgNCiAgeCA9IHZhbF90cmFpbl9YeVtbeF1dLA0KICBwcm9icyA9IGMoMC4wLCAwLjk5OSksDQogIG5hLnJtID0gVA0KKQ0KDQpxcW5vcm0oeSA9IFdpbl9zcXJ0X3gsIHlsYWIgPSAnV2luX3NxcnRfeCcpDQpxcWxpbmUoeSA9IFdpbl9zcXJ0X3gsIHlsYWIgPSAnV2luX3NxcnRfeCcpDQoNCldpbl9yYXdfeCA9IFdpbnNvcml6ZSgNCiAgeCA9IHZhbF90cmFpbl9YeSRUb3RCYXRocywNCiAgcHJvYnMgPSBjKDAsIDAuOTk4KSwNCiAgbmEucm0gPSBUDQopDQoNCnFxbm9ybSh5ID0gV2luX3Jhd194LCB5bGFiID0gJ1dpbl9yYXdfeCcpDQpxcWxpbmUoeSA9IFdpbl9yYXdfeCwgeWxhYiA9ICdXaW5fcmF3X3gnKQ0KDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IHZhbF90cmFpbl9YeSRUb3RCYXRocykpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IHZhbF90cmFpbl9YeSRgc3FydChUb3RCYXRocylgKSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gV2luX3NxcnRfeCkpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IFdpbl9yYXdfeCkpDQpgYGANCg0KTWF5YmUganVzdCBsaWdodGx5IHRvcC1jb2RpbmcgdGhlIHJhdyB2YXJpYWJsZSBpcyBiZXN0Lg0KDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKA0KICAgICdXaW4oc3FydChUb3RCYXRocykpJyA9IFdpbnNvcml6ZSgNCiAgICAgIHNxcnQoVG90QmF0aHMpLA0KICAgICAgcHJvYnMgPSBjKDAsIDAuOTk5KSwNCiAgICAgIG5hLnJtID0gVA0KICAgICkNCiAgKSAlPiUNCiAgbXV0YXRlKA0KICAgICdXaW4oVG90QmF0aHMpJyA9IFdpbnNvcml6ZSgNCiAgICAgIFRvdEJhdGhzLA0KICAgICAgcHJvYnMgPSBjKDAsIDAuOTk4KSwNCiAgICAgIG5hLnJtID0gVA0KICAgICkNCiAgKQ0KYGBgDQoNCiMjIyBDb3JyZWxhdGlvbnMNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdXaW4oVG90QmF0aHMpJw0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeF9sc3QgPSBjKCdUb3RCYXRocycsICdzcXJ0KFRvdEJhdGhzKScsICdXaW4oc3FydChUb3RCYXRocykpJywgJ1dpbihUb3RCYXRocyknKQ0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAhaXMubmEoLmRhdGFbW3hdXSkNCiAgKSwNCiAgeF9sc3QgPSB4X2xzdCwNCiAgZmVhdHMgPSBudW1fZmVhdHMNCikNCmRmDQpwcmludCgiU3VtbWFyeSBvZiBhYnNvbHV0ZSB2YWx1ZXMgb2YgUGVhcnNvbidzIFJzOiIpDQpkZiA9IGFicyhkZikNCnN1bW1hcnkoYWJzKGRmKSkNCg0KZGYgPSBtZWx0KGRmKQ0KZ2dwbG90KGRmLCBhZXMoeCA9IHZhcmlhYmxlLCB5ID0gdmFsdWUpKSArDQogIGdlb21fYm94cGxvdChub3RjaCA9IFQpICsNCiAgeWxhYihsYWJlbCA9ICdBYnNvbHV0ZSBWYWx1ZSBvZiBDb3JyZWxhdGlvbiB0byBPdGhlciBGZWF0dXJlcycpDQoNCnlfbHN0ID0gYygnbG9nKFNhbGVQcmljZSknKQ0KZm9yIChmZWF0IGluIHhfbHN0KSB7DQogIHBsb3Rfc2NhdF9wYWlycyhkZiA9IHZhbF90cmFpbl9YeSwgeCA9IGZlYXQsIHlfbHN0ID0geV9sc3QpDQp9DQpgYGANCg0KIyMjIEhhcmQgQ29kZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ1dpbihUb3RCYXRocyknDQoNCm1pbl92YWwgPSBtaW4oV2luX3Jhd194KQ0KbWF4X3ZhbCA9IG1heChXaW5fcmF3X3gpDQpwcmludChwYXN0ZSgibWluX3ZhbDoiLCBtaW5fdmFsKSkNCnByaW50KHBhc3RlKCJtYXhfdmFsOiIsIG1heF92YWwpKQ0KDQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgNCiAgICAnV2luKFRvdEJhdGhzKScgPSBXaW5zb3JpemUoDQogICAgICBGdWxsQmF0aCArIEJzbXRGdWxsQmF0aCArIDAuNSpIYWxmQmF0aCArIDAuNSpCc210SGFsZkJhdGgsDQogICAgICAjIHByb2JzID0gYygwLjAwMiwgMC45OTgpLA0KICAgICAgIyBuYS5ybSA9IFQNCiAgICAgIG1pbnZhbCA9IG1pbl92YWwsDQogICAgICBtYXh2YWwgPSBtYXhfdmFsDQogICAgKQ0KICApICU+JQ0KICBzZWxlY3QoLWMoJ3NxcnQoVG90QmF0aHMpJywgJ1dpbihzcXJ0KFRvdEJhdGhzKSknKSkNCg0KZ2cgPSBnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IC5kYXRhW1t4XV0pKQ0KcDEgPSBnZyArIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC41KQ0KcDIgPSBnZyArIGdlb21fYm94cGxvdChub3RjaCA9IFQpDQpncmlkLmFycmFuZ2UocDEsIHAyKQ0KYGBgDQoNCiMjIEJlZHJvb21BYnZHciANCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KIyMjIE5vcm1hbGl6ZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ0JlZHJvb21BYnZHcicNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQoNCmRmID0gc2VsZWN0KHZhbF90cmFpbl9YeSwgYygnbG9nKFNhbGVQcmljZSknLCAnQmVkcm9vbUFidkdyJykpDQpkZiRCZWRyb29tQWJ2R3IuZmFjdCA9IGZhY3RvcihkZiRCZWRyb29tQWJ2R3IpDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IGRmLCB4ID0gJ0JlZHJvb21BYnZHci5mYWN0JywgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKA0KICBkYXRhID0gZGYsDQogIHggPSAnQmVkcm9vbUFidkdyLmZhY3QnLA0KICB6ID0geSwNCiAgbWluX24gPSAzMA0KKQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQoNCg0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeF9sc3QgPSBjKCdCZWRyb29tQWJ2R3InKQ0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAhaXMubmEoLmRhdGFbW3hdXSkNCiAgKSwNCiAgeF9sc3QgPSB4X2xzdCwNCiAgZmVhdHMgPSBudW1fZmVhdHMNCikNCmRmDQpwcmludCgiU3VtbWFyeSBvZiBhYnNvbHV0ZSB2YWx1ZXMgb2YgUGVhcnNvbidzIFJzOiIpDQpkZiA9IGFicyhkZikNCnN1bW1hcnkoYWJzKGRmKSkNCg0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAwLjUsDQogIHRfYmludyA9IDAuMQ0KKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnc3FydChCZWRyb29tQWJ2R3IpJw0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoJ3NxcnQoQmVkcm9vbUFidkdyKScgPSBzcXJ0KEJlZHJvb21BYnZHcikpDQoNCiMgUmVjYWxjdWxhdGUgYmVzdCBub3JtYWxpemVycy4NCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCmJlc3Rfbm9ybWFsaXplcnMgPSBmaW5kX2Jlc3Rfbm9ybWFsaXplcl9wZXJfZmVhdCgNCiAgZGYgPSB2YWxfdHJhaW5fWHksDQogIGZlYXRzX2xzdCA9IG51bV9mZWF0cywNCiAgZnVuY3NfbHN0ID0gZnVuY3NfbHN0LA0KICBleGNsdWRlX3ZhbHMgPSBsaXN0KDApDQopDQoNCnN1bW1hcnkodmFsX3RyYWluX1h5W3hdKQ0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAuMSwNCiAgdF9iaW53ID0gLjENCikNCmBgYA0KDQojIyMgV2luc29yaXplDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnc3FydChCZWRyb29tQWJ2R3IpJw0KDQpxcW5vcm0oeSA9IHZhbF90cmFpbl9YeSRCZWRyb29tQWJ2R3IsIHlsYWIgPSAnQmVkcm9vbUFidkdyJykNCnFxbGluZSh5ID0gdmFsX3RyYWluX1h5JEJlZHJvb21BYnZHciwgeWxhYiA9ICdCZWRyb29tQWJ2R3InKQ0KDQpxcW5vcm0oeSA9IHZhbF90cmFpbl9YeVtbeF1dLCB5bGFiID0geCkNCnFxbGluZSh5ID0gdmFsX3RyYWluX1h5W1t4XV0sIHlsYWIgPSB4KQ0KDQpXaW5fc3FydF94ID0gV2luc29yaXplKA0KICB4ID0gdmFsX3RyYWluX1h5W1t4XV0sDQogIHByb2JzID0gYygwLCAwLjk5OSksDQogIG5hLnJtID0gVA0KKQ0KDQpxcW5vcm0oeSA9IFdpbl9zcXJ0X3gsIHlsYWIgPSAnV2luX3NxcnRfeCcpDQpxcWxpbmUoeSA9IFdpbl9zcXJ0X3gsIHlsYWIgPSAnV2luX3NxcnRfeCcpDQoNCldpbl9yYXdfeCA9IFdpbnNvcml6ZSgNCiAgeCA9IHZhbF90cmFpbl9YeSRCZWRyb29tQWJ2R3IsDQogIHByb2JzID0gYygwLCAwLjk5NSksDQogIG5hLnJtID0gVA0KKQ0KDQpxcW5vcm0oeSA9IFdpbl9yYXdfeCwgeWxhYiA9ICdXaW5fcmF3X3gnKQ0KcXFsaW5lKHkgPSBXaW5fcmF3X3gsIHlsYWIgPSAnV2luX3Jhd194JykNCg0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSB2YWxfdHJhaW5fWHkkQmVkcm9vbUFidkdyKSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gdmFsX3RyYWluX1h5JGBzcXJ0KEJlZHJvb21BYnZHcilgKSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gV2luX3NxcnRfeCkpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IFdpbl9yYXdfeCkpDQpgYGANCg0KTWF5YmUganVzdCBsaWdodGx5IFdpbnNvcml6aW5nIHRoZSByYXcgdmFyaWFibGUgaXMgYmVzdC4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoDQogICAgJ1dpbihzcXJ0KEJlZHJvb21BYnZHcikpJyA9IFdpbnNvcml6ZSgNCiAgICAgIHNxcnQoQmVkcm9vbUFidkdyKSwNCiAgICAgIHByb2JzID0gYygwLCAwLjk5OSksDQogICAgICBuYS5ybSA9IFQNCiAgICApDQogICkgJT4lDQogIG11dGF0ZSgNCiAgICAnV2luKEJlZHJvb21BYnZHciknID0gV2luc29yaXplKA0KICAgICAgQmVkcm9vbUFidkdyLA0KICAgICAgcHJvYnMgPSBjKDAsIDAuOTk1KSwNCiAgICAgIG5hLnJtID0gVA0KICAgICkNCiAgKQ0KYGBgDQoNCg0KIyMjIENvcnJlbGF0aW9ucw0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ1dpbihCZWRyb29tQWJ2R3IpJw0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeF9sc3QgPSBjKCdCZWRyb29tQWJ2R3InLCAnc3FydChCZWRyb29tQWJ2R3IpJywgJ1dpbihzcXJ0KEJlZHJvb21BYnZHcikpJywgJ1dpbihCZWRyb29tQWJ2R3IpJykNCg0KZGYgPSBnZXRfY29ycygNCiAgZGF0YSA9IGZpbHRlcigNCiAgICBzZWxlY3QodmFsX3RyYWluX1h5LCBhbGxfb2YobnVtX2ZlYXRzKSksDQogICAgIWlzLm5hKC5kYXRhW1t4XV0pDQogICksDQogIHhfbHN0ID0geF9sc3QsDQogIGZlYXRzID0gbnVtX2ZlYXRzDQopDQpkZg0KcHJpbnQoIlN1bW1hcnkgb2YgYWJzb2x1dGUgdmFsdWVzIG9mIFBlYXJzb24ncyBSczoiKQ0KZGYgPSBhYnMoZGYpDQpzdW1tYXJ5KGFicyhkZikpDQoNCmRmID0gbWVsdChkZikNCmdncGxvdChkZiwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHZhbHVlKSkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArDQogIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMnKQ0KDQp5X2xzdCA9IGMoJ2xvZyhTYWxlUHJpY2UpJykNCmZvciAoZmVhdCBpbiB4X2xzdCkgew0KICBwbG90X3NjYXRfcGFpcnMoZGYgPSB2YWxfdHJhaW5fWHksIHggPSBmZWF0LCB5X2xzdCA9IHlfbHN0KQ0KfQ0KYGBgDQoNCiMjIyBIYXJkIENvZGUNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdXaW4oQmVkcm9vbUFidkdyKScNCg0KbWluX3ZhbCA9IG1pbihXaW5fcmF3X3gpDQptYXhfdmFsID0gbWF4KFdpbl9yYXdfeCkNCnByaW50KHBhc3RlKCJtaW5fdmFsOiIsIG1pbl92YWwpKQ0KcHJpbnQocGFzdGUoIm1heF92YWw6IiwgbWF4X3ZhbCkpDQoNCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKA0KICAgICdXaW4oQmVkcm9vbUFidkdyKScgPSBXaW5zb3JpemUoDQogICAgICBCZWRyb29tQWJ2R3IsDQogICAgICAjIHByb2JzID0gYygwLjAwMiwgMC45OTgpLA0KICAgICAgIyBuYS5ybSA9IFQNCiAgICAgIG1pbnZhbCA9IG1pbl92YWwsDQogICAgICBtYXh2YWwgPSBtYXhfdmFsDQogICAgKQ0KICApICU+JQ0KICBzZWxlY3QoLWMoJ3NxcnQoQmVkcm9vbUFidkdyKScsICdXaW4oc3FydChCZWRyb29tQWJ2R3IpKScpKQ0KDQpnZyA9IGdncGxvdCh2YWxfdHJhaW5fWHksIGFlcyh4ID0gLmRhdGFbW3hdXSkpDQpwMSA9IGdnICsgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxKQ0KcDIgPSBnZyArIGdlb21fYm94cGxvdChub3RjaCA9IFQpDQpncmlkLmFycmFuZ2UocDEsIHAyKQ0KYGBgDQoNCiMjIEtpdGNoZW5BYnZHcg0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzdW1tYXJ5KHZhbF90cmFpbl9YeSRLaXRjaGVuQWJ2R3IpDQp2YWxfdHJhaW5fWHkgPSBzZWxlY3QodmFsX3RyYWluX1h5LCAtYygnS2l0Y2hlbkFidkdyJykpDQpgYGANCg0KIyMgS2l0Y2hlblF1YWwNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdLaXRjaGVuUXVhbCcNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KDQpwX3ZhbHMgPSBnZXRfc2lnbmlmX2xldmVscyhkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeiA9IHksIG1pbl9uID0gMjkpDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCmBgYA0KDQojIyBUb3RSbXNBYnZHcmQNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdUb3RSbXNBYnZHcmQnDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0KDQpkZiA9IHNlbGVjdCh2YWxfdHJhaW5fWHksIGMoJ2xvZyhTYWxlUHJpY2UpJywgJ1RvdFJtc0FidkdyZCcpKQ0KZGYkVG90Um1zQWJ2R3JkLmZhY3QgPSBmYWN0b3IoZGYkVG90Um1zQWJ2R3JkKQ0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSBkZiwgeCA9ICdUb3RSbXNBYnZHcmQuZmFjdCcsIHkgPSB5KQ0KDQpwX3ZhbHMgPSBnZXRfc2lnbmlmX2xldmVscygNCiAgZGF0YSA9IGRmLA0KICB4ID0gJ1RvdFJtc0FidkdyZC5mYWN0JywNCiAgeiA9IHksDQogIG1pbl9uID0gMzApDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCg0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbeF0pDQpzdW1fYW5kX3RyYW5zX2NvbnQoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHksDQogIHggPSB4LA0KICBmdW5jID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRmdW5jLA0KICBmdW5jX25hbWUgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJG5hbWUsDQogIHhfYmludyA9IDEsDQogIHRfYmludyA9IDENCikNCg0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeF9sc3QgPSBjKCdUb3RSbXNBYnZHcmQnKQ0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAhaXMubmEoLmRhdGFbW3hdXSkNCiAgKSwNCiAgeF9sc3QgPSB4X2xzdCwNCiAgZmVhdHMgPSBudW1fZmVhdHMNCikNCmRmDQpwcmludCgiU3VtbWFyeSBvZiBhYnNvbHV0ZSB2YWx1ZXMgb2YgUGVhcnNvbidzIFJzOiIpDQpkZiA9IGFicyhkZikNCnN1bW1hcnkoYWJzKGRmKSkNCmBgYA0KDQojIyMgV2luc29yaXplDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnVG90Um1zQWJ2R3JkJw0KDQpxcW5vcm0oeSA9IHZhbF90cmFpbl9YeVtbeF1dLCB5bGFiID0geCkNCnFxbGluZSh5ID0gdmFsX3RyYWluX1h5W1t4XV0sIHlsYWIgPSB4KQ0KDQpXaW5fcmF3X3ggPSBXaW5zb3JpemUoDQogIHggPSB2YWxfdHJhaW5fWHkkVG90Um1zQWJ2R3JkLA0KICBwcm9icyA9IGMoMCwgMC45NzUpLA0KICBuYS5ybSA9IFQNCikNCg0KcXFub3JtKHkgPSBXaW5fcmF3X3gsIHlsYWIgPSAnV2luKFRvdFJtc0FidkdyZCknKQ0KcXFsaW5lKHkgPSBXaW5fcmF3X3gsIHlsYWIgPSAnV2luKFRvdFJtc0FidkdyZCknKQ0KDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IHZhbF90cmFpbl9YeSRUb3RSbXNBYnZHcmQpKQ0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBXaW5fcmF3X3gpKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKA0KICAgICdXaW4oVG90Um1zQWJ2R3JkKScgPSBXaW5zb3JpemUoDQogICAgICBUb3RSbXNBYnZHcmQsDQogICAgICBwcm9icyA9IGMoMCwgMC45NzUpLA0KICAgICAgbmEucm0gPSBUDQogICAgKQ0KICApDQpgYGANCg0KIyMjIENvcnJlbGF0aW9ucw0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ1dpbihUb3RSbXNBYnZHcmQpJw0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeF9sc3QgPSBjKCdUb3RSbXNBYnZHcmQnLCAnV2luKFRvdFJtc0FidkdyZCknKQ0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAhaXMubmEoLmRhdGFbW3hdXSkNCiAgKSwNCiAgeF9sc3QgPSB4X2xzdCwNCiAgZmVhdHMgPSBudW1fZmVhdHMNCikNCmRmDQpwcmludCgiU3VtbWFyeSBvZiBhYnNvbHV0ZSB2YWx1ZXMgb2YgUGVhcnNvbidzIFJzOiIpDQpkZiA9IGFicyhkZikNCnN1bW1hcnkoYWJzKGRmKSkNCg0KZGYgPSBtZWx0KGRmKQ0KZ2dwbG90KGRmLCBhZXMoeCA9IHZhcmlhYmxlLCB5ID0gdmFsdWUpKSArDQogIGdlb21fYm94cGxvdChub3RjaCA9IFQpICsNCiAgeWxhYihsYWJlbCA9ICdBYnNvbHV0ZSBWYWx1ZSBvZiBDb3JyZWxhdGlvbiB0byBPdGhlciBGZWF0dXJlcycpDQoNCnlfbHN0ID0gYygnbG9nKFNhbGVQcmljZSknKQ0KZm9yIChmZWF0IGluIHhfbHN0KSB7DQogIHBsb3Rfc2NhdF9wYWlycyhkZiA9IHZhbF90cmFpbl9YeSwgeCA9IGZlYXQsIHlfbHN0ID0geV9sc3QpDQp9DQpgYGANCg0KIyMjIEhhcmQgQ29kZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ1dpbihUb3RSbXNBYnZHcmQpJw0KDQptaW5fdmFsID0gbWluKFdpbl9yYXdfeCkNCm1heF92YWwgPSBtYXgoV2luX3Jhd194KQ0KcHJpbnQocGFzdGUoIm1pbl92YWw6IiwgbWluX3ZhbCkpDQpwcmludChwYXN0ZSgibWF4X3ZhbDoiLCBtYXhfdmFsKSkNCg0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoDQogICAgJ1dpbihUb3RSbXNBYnZHcmQpJyA9IFdpbnNvcml6ZSgNCiAgICAgIFRvdFJtc0FidkdyZCwNCiAgICAgICMgcHJvYnMgPSBjKDAsIDAuOTc1KSwNCiAgICAgICMgbmEucm0gPSBUDQogICAgICBtaW52YWwgPSBtaW5fdmFsLA0KICAgICAgbWF4dmFsID0gbWF4X3ZhbA0KICAgICkNCiAgKQ0KDQpnZyA9IGdncGxvdCh2YWxfdHJhaW5fWHksIGFlcyh4ID0gLmRhdGFbW3hdXSkpDQpwMSA9IGdnICsgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxKQ0KcDIgPSBnZyArIGdlb21fYm94cGxvdChub3RjaCA9IFQpDQpncmlkLmFycmFuZ2UocDEsIHAyKQ0KYGBgDQoNCiMjIEZ1bmN0aW9uYWwNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQp4ID0gJ0Z1bmN0aW9uYWwnDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5LCBtaW5fbiA9IDI5KQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQpgYGANCg0KIyMgRmlyZXBsYWNlcyANCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdGaXJlcGxhY2VzJw0KeSA9ICdTYWxlUHJpY2UnDQpzdW1tYXJpemVfYnkoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KDQp2YWxfdHJhaW5fWHkkRmlyZXBsYWNlcy5mYWN0ID0gZmFjdG9yKHZhbF90cmFpbl9YeSRGaXJlcGxhY2VzLCBvcmRlcmVkID0gVCkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9ICdGaXJlcGxhY2VzLmZhY3QnLCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9ICdGaXJlcGxhY2VzLmZhY3QnLCB6ID0geSwgbWluX24gPSAzMCkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXkgPSBGDQogICkNCg0KcHJpbnQoDQogICAgcGFzdGUoDQogICAgICAiTGV2ZWxzIHcvIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IiwNCiAgICAgIHksDQogICAgICAidGhhbiBhbm90aGVyIGxldmVsOiINCiAgICApDQogICkNCnByaW50KHBfdmFscyRzaWduaWZfbGV2ZWxzKQ0KDQpudW1fZmVhdHMgPSBjb2xuYW1lcyhzZWxlY3QodmFsX3RyYWluX1h5LCB3aGVyZShpcy5udW1lcmljKSkpDQp4X2xzdCA9IGMoJ0ZpcmVwbGFjZXMnKQ0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAhaXMubmEoLmRhdGFbW3hdXSkNCiAgKSwNCiAgeF9sc3QgPSB4X2xzdCwNCiAgZmVhdHMgPSBudW1fZmVhdHMNCikNCmRmDQpwcmludCgiU3VtbWFyeSBvZiBhYnNvbHV0ZSB2YWx1ZXMgb2YgUGVhcnNvbidzIFJzOiIpDQpkZiA9IGFicyhkZikNCnN1bW1hcnkoYWJzKGRmKSkNCg0KZGYgPSBtZWx0KGRmKQ0KZ2dwbG90KGRmLCBhZXMoeCA9IHZhcmlhYmxlLCB5ID0gdmFsdWUpKSArDQogIGdlb21fYm94cGxvdChub3RjaCA9IFQpICsNCiAgeWxhYihsYWJlbCA9ICdBYnNvbHV0ZSBWYWx1ZSBvZiBDb3JyZWxhdGlvbiB0byBPdGhlciBGZWF0dXJlcycpDQoNCnBsb3Rfc2NhdF9wYWlycyhkZiA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHlfbHN0ID0gYyh5KSkNCmBgYA0KDQpUaGUgdmFyaWFibGUgbmVlZHMgbW9yZSB0aGFuIHRocmVlIHVuaXF1ZSB2YWx1ZXMgaW4gb3JkZXIgdG8gdXNlIG15IGZ1bmN0aW9uIHRvIGNoZWNrIGZvciB0cmFuc2Zvcm1hdGlvbnMsIGFuZCAwIGNhbid0IGJlIG9uZSBvZiB0aGVtIGluIG9yZGVyIHRvIGluY2x1ZGUgbG9nIHRyYW5zZm9ybWF0aW9ucy4gU28sIEknbGwgdXNlIEZpcmVwbGFjZXMgKyAxIHRvIHNlYXJjaCBmb3IgdHJhbnNmb3JtYXRpb25zLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp2YWxfdHJhaW5fWHkkRmlyZXBsYWNlc19wbHVzMSA9IHZhbF90cmFpbl9YeSRGaXJlcGxhY2VzICsgMQ0KeCA9ICdGaXJlcGxhY2VzX3BsdXMxJw0KDQojIFJlY2FsY3VsYXRlIGJlc3Qgbm9ybWFsaXplcnMuDQpudW1fZmVhdHMgPSBjb2xuYW1lcyhzZWxlY3QodmFsX3RyYWluX1h5LCB3aGVyZShpcy5udW1lcmljKSkpDQpiZXN0X25vcm1hbGl6ZXJzID0gZmluZF9iZXN0X25vcm1hbGl6ZXJfcGVyX2ZlYXQoDQogIGRmID0gdmFsX3RyYWluX1h5LA0KICBmZWF0c19sc3QgPSBudW1fZmVhdHMsDQogIGZ1bmNzX2xzdCA9IGZ1bmNzX2xzdCwNCiAgZXhjbHVkZV92YWxzID0gbGlzdCgwKQ0KKQ0KDQpzdW1fYW5kX3RyYW5zX2NvbnQoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHksDQogIHggPSB4LA0KICBmdW5jID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRmdW5jLA0KICBmdW5jX25hbWUgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJG5hbWUsDQogIHhfYmludyA9IDEsDQogIHRfYmludyA9IDENCikNCmBgYA0KDQojIyMgV2luc29yaXplDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnRmlyZXBsYWNlcycNCg0KcXFub3JtKHkgPSB2YWxfdHJhaW5fWHlbW3hdXSwgeWxhYiA9IHgpDQpxcWxpbmUoeSA9IHZhbF90cmFpbl9YeVtbeF1dLCB5bGFiID0geCkNCg0KV2luX3Jhd194ID0gV2luc29yaXplKA0KICB4ID0gdmFsX3RyYWluX1h5JEZpcmVwbGFjZXMsDQogIHByb2JzID0gYygwLCAwLjk5OSksDQogIG5hLnJtID0gVA0KKQ0KDQpxcW5vcm0oeSA9IFdpbl9yYXdfeCwgeWxhYiA9ICdXaW5fcmF3X3gnKQ0KcXFsaW5lKHkgPSBXaW5fcmF3X3gsIHlsYWIgPSAnV2luX3Jhd194JykNCg0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSB2YWxfdHJhaW5fWHkkRmlyZXBsYWNlcykpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IFdpbl9yYXdfeCkpDQpgYGANCg0KV2luc29yaXphdGlvbiBkb2Vzbid0IGhlbHAuIA0KDQojIyMgQmluYXJpemUNCg0KVGhlcmUncyBubyBhcHBhcmVudCBtZWFuaW5nZnVsIGRpZmZlcmVuY2UgaW4gcHJpY2UgYmV0d2VlbiBob3VzZXMgd2l0aCAxIGZpcmVwbGFjZSBhbmQgdGhvc2Ugd2l0aCAyIG9yIDMuIEl0J3Mgc2lnbmlmaWNhbnQgKHAgPCAwLjEpLCBidXQgbm90IGFwcGFyZW50bHkgbWVhbmluZ2Z1bCBnaXZlbiB0aGUgb3ZlcmxhcHBpbmcgbm90Y2hlcyBpbiB0aGUgYm94cGxvdCBhYm92ZSwgdGhvdWdoIEkgZGlkIG5vdCB0YWtlIENvaGVuJ3MgZCBmb3IgZWZmZWN0IHNpemUuDQoNCkJpbmFyaXphdGlvbiBtYXkgY29uZGVuc2UgdGhlIGZlYXR1cmUgc3BhY2Ugd2l0aG91dCBhIGxvdCBvZiBpbmZvcm1hdGlvbiBsb3NzLiBJbiBmYWN0LCBpdCBpbXByb3ZlcyB0aGUgY29ycmVsYXRpb24gdG8gdGhlIHRhcmdldCB2YXJpYWJsZS4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoRmlyZXBsYWNlcy5iaW4gPSBpZmVsc2UoRmlyZXBsYWNlcyA9PSAwLCAwLCAxKSkNCg0KeCA9ICdGaXJlcGxhY2VzLmJpbicNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KdmFsX3RyYWluX1h5JEZpcmVwbGFjZXMuYmluLmZhY3QgPSBmYWN0b3IodmFsX3RyYWluX1h5JEZpcmVwbGFjZXMuYmluLCBvcmRlcmVkID0gVCkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9ICdGaXJlcGxhY2VzLmJpbi5mYWN0JywgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSAnRmlyZXBsYWNlcy5iaW4uZmFjdCcsIHogPSB5LCBtaW5fbiA9IDMwKQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQoNCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCnhfbHN0ID0gYygnRmlyZXBsYWNlcycsICdGaXJlcGxhY2VzLmJpbicpDQoNCmRmID0gZ2V0X2NvcnMoDQogIGRhdGEgPSBmaWx0ZXIoDQogICAgc2VsZWN0KHZhbF90cmFpbl9YeSwgYWxsX29mKG51bV9mZWF0cykpLA0KICAgICFpcy5uYSguZGF0YVtbeF1dKQ0KICApLA0KICB4X2xzdCA9IHhfbHN0LA0KICBmZWF0cyA9IG51bV9mZWF0cw0KKQ0KZGYNCnByaW50KCJTdW1tYXJ5IG9mIGFic29sdXRlIHZhbHVlcyBvZiBQZWFyc29uJ3MgUnM6IikNCmRmID0gYWJzKGRmKQ0Kc3VtbWFyeShhYnMoZGYpKQ0KDQpkZiA9IG1lbHQoZGYpDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gdmFyaWFibGUsIHkgPSB2YWx1ZSkpICsNCiAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKw0KICB5bGFiKGxhYmVsID0gJ0Fic29sdXRlIFZhbHVlIG9mIENvcnJlbGF0aW9uIHRvIE90aGVyIEZlYXR1cmVzJykNCmBgYA0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp2YWxfdHJhaW5fWHkgPSBzZWxlY3QoDQogIHZhbF90cmFpbl9YeSwNCiAgLWMoJ0ZpcmVwbGFjZXMuZmFjdCcsICdGaXJlcGxhY2VzX3BsdXMxJywgJ0ZpcmVwbGFjZXMuYmluLmZhY3QnLA0KICAgICAnRmlyZXBsYWNlcycpDQopDQpgYGANCg0KIyMgRmlyZXBsYWNlUXUNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdGaXJlcGxhY2VRdScNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KDQpwX3ZhbHMgPSBnZXRfc2lnbmlmX2xldmVscyhkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeiA9IHksIG1pbl9uID0gMjkpDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCmBgYA0KDQpPbmUtaG90IGVuY29kaW5nIHRoaXMgRmlyZXBsYWNlUXUgd2lsbCBpbmNsdWRlIGEgYmluYXJpemVkIHZlcnNpb24gKCJOb25lIjppbnRbMCwxXSksIHNvIHdlIGNhbiBkcm9wIEZpcmVwbGFjZS5iaW4gYWx0b2dldGhlciwgYW5kIEZpcmVwbGFjZSBiZWNhdXNlIGl0IGp1c3QgYWRkcyBub2lzZS4gSSdsbCB0YWtlIGNhcmUgb2YgdGhhdCBkdXJpbmcgbW9kZWxpbmcgd2l0aCBjYXJldC4NCg0KIyMgR2FyYWdlVHlwZQ0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ0dhcmFnZVR5cGUnDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5LCBtaW5fbiA9IDMwKQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQpgYGANCg0KIyMgR2FyYWdlWXJCbHQgDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNClNpbWlsYXIgZGlzdHJpYnV0aW9uIGFzIHllYXIgYnVpbHQgZm9yIGhvdXNlLiBNYXkgbm90IGFkZCBhZGRpdGlvbmFsIGluZm9ybWF0aW9uLCBidXQgbWF5IGJlIHVzZWZ1bCBpbiBpbnRlcmFjdGlvbiB3aXRoIHR5cGUgYW5kIGZpbmlzaC4gQ291bGQgZHJvcCBhbmQgdXNlIFllYXJCdWlsdCBhcyBwcm94eSBidXQgd291bGQgbG9zZSBzb21lIGluZm8uDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnR2FyYWdlWXJCbHQnDQpzdW1tYXJ5KHZhbF90cmFpbl9YeVt4XSkNCiMgc3VtX2FuZF90cmFuc19jb250KA0KIyAgIGRhdGEgPSB2YWxfdHJhaW5fWHksDQojICAgeCA9IHgsDQojICAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiMgICBmdW5jX25hbWUgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJG5hbWUsDQojICAgeF9iaW53ID0gMSwNCiMgICB0X2JpbncgPSAxDQojICkNCg0KZ2cgPSBnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IEdhcmFnZVlyQmx0KSkNCnAxID0gZ2cgKyBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEpDQpwMiA9IGdnICsgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkNCmdyaWQuYXJyYW5nZShwMSwgcDIpDQpgYGANCg0KIyMjIENvcnJlbGF0aW9ucw0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ0dhcmFnZVlyQmx0Jw0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeF9sc3QgPSBjKHgpDQoNCmRmID0gZ2V0X2NvcnMoDQogIGRhdGEgPSBmaWx0ZXIoDQogICAgc2VsZWN0KHZhbF90cmFpbl9YeSwgYWxsX29mKG51bV9mZWF0cykpLA0KICAgICFpcy5uYSguZGF0YVtbeF1dKQ0KICApLA0KICB4X2xzdCA9IHhfbHN0LA0KICBmZWF0cyA9IG51bV9mZWF0cw0KKQ0KZGYNCnByaW50KCJTdW1tYXJ5IG9mIGFic29sdXRlIHZhbHVlcyBvZiBQZWFyc29uJ3MgUnM6IikNCmRmID0gYWJzKGRmKQ0Kc3VtbWFyeShhYnMoZGYpKQ0KDQpkZiA9IG1lbHQoZGYpDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gdmFyaWFibGUsIHkgPSB2YWx1ZSkpICsNCiAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKw0KICB5bGFiKGxhYmVsID0gJ0Fic29sdXRlIFZhbHVlIG9mIENvcnJlbGF0aW9uIHRvIE90aGVyIEZlYXR1cmVzJykNCg0KeV9sc3QgPSBjKCdsb2coU2FsZVByaWNlKScpDQpwbG90X3NjYXRfcGFpcnMoZGYgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5X2xzdCA9IHlfbHN0KQ0KYGBgDQoNCjxhIGlkPSJnYXJibHRDb250cm9sIj48L2E+DQoNCiMjIyAiQ29udHJvbGxpbmciDQoNCkV2ZW4gImNvbnRyb2xsaW5nIiBmb3IgdGhlIHllYXIgdGhlIGhvdXNlIHdhcyBidWlsdCwgR2FyYWdlWXJCbHQgZG9lc24ndCBwcmVkaWN0IHNhbGUgcHJpY2VzIHdlbGwsIGFzIHNlZW4gaW4gdGhlIG5leHQgZmV3IHBsb3RzLiBJJ2xsIGp1c3QgZHJvcCBpdC4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoDQogICAgJ0dhckJsdExhdGVyJyA9IGZhY3RvcihpZmVsc2UoKEdhcmFnZVlyQmx0IC0gWWVhckJ1aWx0KSA8PSAwLCAwLCAxKSkNCiAgKQ0KDQpnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IEdhcmFnZVlyQmx0LCB5ID0gWWVhckJ1aWx0KSkgKw0KICBnZW9tX2ppdHRlcigNCiAgICBhbHBoYSA9IDAuNSwNCiAgICBhZXMoDQogICAgICBjb2xvciA9IFNhbGVQcmljZS5mYWN0DQogICAgKSwNCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3ID0gMSwgaCA9IDEpDQogICkNCg0KZmJ2ID0gZmVuY2VkX2pidigNCiAgZGF0YSA9IHZhbF90cmFpbl9YeSwNCiAgeCA9ICdHYXJCbHRMYXRlcicsDQogIHkgPSAnbG9nKFNhbGVQcmljZSknDQopDQpmYnYNCg0KZmJ2ICsNCiAgZmFjZXRfd3JhcChmYWNldHMgPSB2YXJzKFllYXJCdWlsdC5mYWN0KSkNCg0KdmFsX3RyYWluX1h5JHJlc2lkcyA9IGxtKA0KICBgbG9nKFNhbGVQcmljZSlgIH4gYHNxcnQoQWdlKWAsDQogIHZhbF90cmFpbl9YeQ0KKSRyZXNpZHVhbHMNCg0KZGYgPSBmaWx0ZXIoDQogIHZhbF90cmFpbl9YeSwNCiAgR2FyYWdlWXJCbHQgLSBZZWFyQnVpbHQgIT0gMA0KKQ0KDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gR2FyYWdlWXJCbHQsIHkgPSByZXNpZHMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICdsbScpICsNCiAgeWxhYihsYWJlbCA9ICdsb2coU2FsZVByaWNlKSB+IFllYXJCdWlsdCBSZXNpZHVhbHMnKQ0KDQpwcmludCgNCiAgcGFzdGUoDQogICAgIkNvcnJlbGF0aW9uIG9mIEdhcmFnZVlSQmx0IHRvIEFnZSByZXNpZHVhbDoiLA0KICAgIGNvcihkZiRHYXJhZ2VZckJsdCwgZGYkcmVzaWRzKQ0KICApDQopDQpgYGANCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5ID0gc2VsZWN0KHZhbF90cmFpbl9YeSwgLWMoJ0dhcmFnZVlyQmx0JywgJ0dhckJsdExhdGVyJykpDQpgYGANCg0KIyMgR2FyYWdlRmluaXNoDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnR2FyYWdlRmluaXNoJw0KeSA9ICdTYWxlUHJpY2UnDQpzdW1tYXJpemVfYnkoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KeSA9ICdsb2coU2FsZVByaWNlKScNCnN1bV9hbmRfdHJhbnNfZmFjdChkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB6ID0geSwgbWluX24gPSAzMCkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXkgPSBGDQogICkNCg0KcHJpbnQoDQogICAgcGFzdGUoDQogICAgICAiTGV2ZWxzIHcvIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IiwNCiAgICAgIHksDQogICAgICAidGhhbiBhbm90aGVyIGxldmVsOiINCiAgICApDQogICkNCnByaW50KHBfdmFscyRzaWduaWZfbGV2ZWxzKQ0KYGBgDQoNCiMjIEdhcmFnZUNhcnMgDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnR2FyYWdlQ2FycycNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KeSA9ICdsb2coU2FsZVByaWNlKScNCnZhbF90cmFpbl9YeSRHYXJhZ2VDYXJzLmZhY3QgPSBmYWN0b3IoDQogIHZhbF90cmFpbl9YeSRHYXJhZ2VDYXJzLA0KICBvcmRlcmVkID0gVA0KKQ0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSAnR2FyYWdlQ2Fycy5mYWN0JywgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSAnR2FyYWdlQ2Fycy5mYWN0JywgeiA9IHksIG1pbl9uID0gMzApDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCg0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeF9sc3QgPSBjKHgpDQoNCmRmID0gZ2V0X2NvcnMoDQogIGRhdGEgPSBmaWx0ZXIoDQogICAgc2VsZWN0KHZhbF90cmFpbl9YeSwgYWxsX29mKG51bV9mZWF0cykpLA0KICAgICFpcy5uYSguZGF0YVtbeF1dKQ0KICApLA0KICB4X2xzdCA9IHhfbHN0LA0KICBmZWF0cyA9IG51bV9mZWF0cw0KKQ0KZGYNCnByaW50KCJTdW1tYXJ5IG9mIGFic29sdXRlIHZhbHVlcyBvZiBQZWFyc29uJ3MgUnM6IikNCmRmID0gYWJzKGRmKQ0Kc3VtbWFyeShhYnMoZGYpKQ0KDQpkZiA9IG1lbHQoZGYpDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gdmFyaWFibGUsIHkgPSB2YWx1ZSkpICsNCiAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKw0KICB5bGFiKGxhYmVsID0gJ0Fic29sdXRlIFZhbHVlIG9mIENvcnJlbGF0aW9uIHRvIE90aGVyIEZlYXR1cmVzJykNCg0KcGxvdF9zY2F0X3BhaXJzKGRmID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeV9sc3QgPSBjKHkpKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSRHYXJhZ2VDYXJzLnBsdXMxID0gdmFsX3RyYWluX1h5JEdhcmFnZUNhcnMgKyAxDQp4ID0gJ0dhcmFnZUNhcnMucGx1czEnDQoNCiMgUmVjYWxjdWxhdGUgYmVzdCBub3JtYWxpemVycy4NCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCmJlc3Rfbm9ybWFsaXplcnMgPSBmaW5kX2Jlc3Rfbm9ybWFsaXplcl9wZXJfZmVhdCgNCiAgZGYgPSB2YWxfdHJhaW5fWHksDQogIGZlYXRzX2xzdCA9IG51bV9mZWF0cywNCiAgZnVuY3NfbHN0ID0gZnVuY3NfbHN0LA0KICBleGNsdWRlX3ZhbHMgPSBsaXN0KDApDQopDQoNCnN1bV9hbmRfdHJhbnNfY29udCgNCiAgZGF0YSA9IHZhbF90cmFpbl9YeSwNCiAgeCA9IHgsDQogIGZ1bmMgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJGZ1bmMsDQogIGZ1bmNfbmFtZSA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkbmFtZSwNCiAgeF9iaW53ID0gMSwNCiAgdF9iaW53ID0gMQ0KKQ0KYGBgDQoNCiMjIyBXaW5zb3JpemUNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdHYXJhZ2VDYXJzJw0KDQpxcW5vcm0oeSA9IHZhbF90cmFpbl9YeVtbeF1dLCB5bGFiID0geCkNCnFxbGluZSh5ID0gdmFsX3RyYWluX1h5W1t4XV0sIHlsYWIgPSB4KQ0KDQp2YWxfdHJhaW5fWHkkYFdpbihHYXJhZ2VDYXJzKWAgPSBXaW5zb3JpemUoDQogIHggPSB2YWxfdHJhaW5fWHlbW3hdXSwNCiAgcHJvYnMgPSBjKDAsIDAuOTk5KSwNCiAgbmEucm0gPSBUDQopDQoNCnFxbm9ybSh5ID0gdmFsX3RyYWluX1h5JGBXaW4oR2FyYWdlQ2FycylgLCB5bGFiID0gJ1dpbl9yYXdfeCcpDQpxcWxpbmUoeSA9IHZhbF90cmFpbl9YeSRgV2luKEdhcmFnZUNhcnMpYCwgeWxhYiA9ICdXaW5fcmF3X3gnKQ0KDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IHZhbF90cmFpbl9YeVtbeF1dKSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gdmFsX3RyYWluX1h5JGBXaW4oR2FyYWdlQ2FycylgKSkNCmBgYA0KDQojIyMgQmluYXJpemUNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoR2FyYWdlQ2Fycy5iaW4gPSBpZmVsc2UoR2FyYWdlQ2FycyA9PSAwLCAwLCAxKSkNCg0KeCA9ICdHYXJhZ2VDYXJzLmJpbicNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KdmFsX3RyYWluX1h5JEdhcmFnZUNhcnMuYmluLmZhY3QgPQ0KICBmYWN0b3IodmFsX3RyYWluX1h5JEdhcmFnZUNhcnMuYmluLCBvcmRlcmVkID0gVCkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9ICdHYXJhZ2VDYXJzLmJpbi5mYWN0JywgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKA0KICBkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0gJ0dhcmFnZUNhcnMuYmluLmZhY3QnLCB6ID0geSwgbWluX24gPSAzMA0KKQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQoNCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCnhfbHN0ID0gYygnR2FyYWdlQ2FycycsICdXaW4oR2FyYWdlQ2FycyknLCAnR2FyYWdlQ2Fycy5iaW4nKQ0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAhaXMubmEoLmRhdGFbW3hdXSkNCiAgKSwNCiAgeF9sc3QgPSB4X2xzdCwNCiAgZmVhdHMgPSBudW1fZmVhdHMNCikNCmRmDQpwcmludCgiU3VtbWFyeSBvZiBhYnNvbHV0ZSB2YWx1ZXMgb2YgUGVhcnNvbidzIFJzOiIpDQpkZiA9IGFicyhkZikNCnN1bW1hcnkoYWJzKGRmKSkNCg0KZGYgPSBtZWx0KGRmKQ0KZ2dwbG90KGRmLCBhZXMoeCA9IHZhcmlhYmxlLCB5ID0gdmFsdWUpKSArDQogIGdlb21fYm94cGxvdChub3RjaCA9IFQpICsNCiAgeWxhYihsYWJlbCA9ICdBYnNvbHV0ZSBWYWx1ZSBvZiBDb3JyZWxhdGlvbiB0byBPdGhlciBGZWF0dXJlcycpDQpgYGANCg0KVGhhdCBkaWRuJ3Qgc2VlbSB0byBoZWxwLg0KDQojIyBHYXJhZ2VBcmVhIA0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQojIyMgTm9ybWFsaXplDQoNClBvbHltb2RhbCBpbiBpbnRlcmFjdGlvbiB3aXRoIEdhcmFnZUNhcnMuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnR2FyYWdlQXJlYScNCnN1bW1hcnkodmFsX3RyYWluX1h5W3hdKQ0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSA1MCwNCiAgdF9iaW53ID0gMQ0KKQ0KYGBgDQoNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdzcXJ0KEdhcmFnZUFyZWEpJw0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoJ3NxcnQoR2FyYWdlQXJlYSknID0gc3FydChHYXJhZ2VBcmVhKSkNCg0KIyBSZWNhbGN1bGF0ZSBiZXN0IG5vcm1hbGl6ZXJzLg0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KYmVzdF9ub3JtYWxpemVycyA9IGZpbmRfYmVzdF9ub3JtYWxpemVyX3Blcl9mZWF0KA0KICBkZiA9IHZhbF90cmFpbl9YeSwNCiAgZmVhdHNfbHN0ID0gbnVtX2ZlYXRzLA0KICBmdW5jc19sc3QgPSBmdW5jc19sc3QsDQogIGV4Y2x1ZGVfdmFscyA9IGxpc3QoMCkNCikNCg0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbeF0pDQpzdW1fYW5kX3RyYW5zX2NvbnQoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHksDQogIHggPSB4LA0KICBmdW5jID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRmdW5jLA0KICBmdW5jX25hbWUgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJG5hbWUsDQogIHhfYmludyA9IDEsDQogIHRfYmludyA9IDENCikNCmBgYA0KDQojIyMgV2luc29yaXplDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnc3FydChHYXJhZ2VBcmVhKScNCmRmID0gZmlsdGVyKHZhbF90cmFpbl9YeSwgR2FyYWdlQXJlYSAhPSAwKQ0KDQpxcW5vcm0oeSA9IGRmJEdhcmFnZUFyZWEsIHlsYWIgPSAnR2FyYWdlQXJlYScpDQpxcWxpbmUoeSA9IGRmJEdhcmFnZUFyZWEsIHlsYWIgPSAnR2FyYWdlQXJlYScpDQoNCnFxbm9ybSh5ID0gZGZbW3hdXSwgeWxhYiA9IHgpDQpxcWxpbmUoeSA9IGRmW1t4XV0sIHlsYWIgPSB4KQ0KDQpXaW5fc3FydF94ID0gV2luc29yaXplKA0KICB4ID0gZGZbW3hdXSwNCiAgcHJvYnMgPSBjKDAsIDAuOTk4KSwNCiAgbmEucm0gPSBUDQopDQoNCnFxbm9ybSh5ID0gV2luX3NxcnRfeCwgeWxhYiA9ICdXaW5fc3FydF94JykNCnFxbGluZSh5ID0gV2luX3NxcnRfeCwgeWxhYiA9ICdXaW5fc3FydF94JykNCg0KV2luX3Jhd194ID0gV2luc29yaXplKA0KICB4ID0gZGYkR2FyYWdlQXJlYSwNCiAgcHJvYnMgPSBjKDAsIDAuOTg3KSwNCiAgbmEucm0gPSBUDQopDQoNCnFxbm9ybSh5ID0gV2luX3Jhd194LCB5bGFiID0gJ1dpbl9yYXdfeCcpDQpxcWxpbmUoeSA9IFdpbl9yYXdfeCwgeWxhYiA9ICdXaW5fcmF3X3gnKQ0KDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IHZhbF90cmFpbl9YeSRHYXJhZ2VBcmVhKSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gdmFsX3RyYWluX1h5JGBzcXJ0KEdhcmFnZUFyZWEpYCkpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IFdpbl9zcXJ0X3gpKQ0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBXaW5fcmF3X3gpKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm1pbl92YWwgPSBtaW4oV2luX3NxcnRfeCkNCm1heF92YWwgPSBtYXgoV2luX3NxcnRfeCkNCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKA0KICAgICdXaW4oc3FydChHYXJhZ2VBcmVhKSknID0gaWZlbHNlKA0KICAgICAgR2FyYWdlQXJlYSA9PSAwLA0KICAgICAgMCwNCiAgICAgIFdpbnNvcml6ZSgNCiAgICAgICAgc3FydChHYXJhZ2VBcmVhKSwNCiAgICAgICAgbWludmFsID0gbWluX3ZhbCwNCiAgICAgICAgbWF4dmFsID0gbWF4X3ZhbA0KICAgICAgKQ0KICAgICkNCiAgKQ0KYGBgDQoNCiMjIyBDb3JyZWxhdGlvbnMNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdXaW4oc3FydChHYXJhZ2VBcmVhKSknDQpudW1fZmVhdHMgPSBjb2xuYW1lcyhzZWxlY3QodmFsX3RyYWluX1h5LCB3aGVyZShpcy5udW1lcmljKSkpDQp4X2xzdCA9IGMoJ0dhcmFnZUFyZWEnLCAnc3FydChHYXJhZ2VBcmVhKScsIHgpDQoNCmRmID0gZ2V0X2NvcnMoDQogIGRhdGEgPSBmaWx0ZXIoDQogICAgc2VsZWN0KHZhbF90cmFpbl9YeSwgYWxsX29mKG51bV9mZWF0cykpLA0KICAgICFpcy5uYSguZGF0YVtbeF1dKQ0KICApLA0KICB4X2xzdCA9IHhfbHN0LA0KICBmZWF0cyA9IG51bV9mZWF0cw0KKQ0KZGYNCnByaW50KCJTdW1tYXJ5IG9mIGFic29sdXRlIHZhbHVlcyBvZiBQZWFyc29uJ3MgUnM6IikNCmRmID0gYWJzKGRmKQ0Kc3VtbWFyeShhYnMoZGYpKQ0KDQpkZiA9IG1lbHQoZGYpDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gdmFyaWFibGUsIHkgPSB2YWx1ZSkpICsNCiAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKw0KICB5bGFiKGxhYmVsID0gJ0Fic29sdXRlIFZhbHVlIG9mIENvcnJlbGF0aW9uIHRvIE90aGVyIEZlYXR1cmVzJykNCg0KZGYgPSBnZXRfY29ycygNCiAgZGF0YSA9IGZpbHRlcigNCiAgICBzZWxlY3QodmFsX3RyYWluX1h5LCBhbGxfb2YobnVtX2ZlYXRzKSksDQogICAgLmRhdGFbW3hdXSAhPSAwDQogICksDQogIHhfbHN0ID0geF9sc3QsDQogIGZlYXRzID0gbnVtX2ZlYXRzDQopDQpkZg0KcHJpbnQoIlN1bW1hcnkgb2YgYWJzb2x1dGUgdmFsdWVzIG9mIFBlYXJzb24ncyBScyAobm8gMHMpOiIpDQpkZiA9IGFicyhkZikNCnN1bW1hcnkoYWJzKGRmKSkNCg0KZGYgPSBtZWx0KGRmKQ0KZ2dwbG90KGRmLCBhZXMoeCA9IHZhcmlhYmxlLCB5ID0gdmFsdWUpKSArDQogIGdlb21fYm94cGxvdChub3RjaCA9IFQpICsNCiAgeWxhYihsYWJlbCA9ICdBYnNvbHV0ZSBWYWx1ZSBvZiBDb3JyZWxhdGlvbiB0byBPdGhlciBGZWF0dXJlcyAobm8gMHMpJykNCg0KeV9sc3QgPSBjKCdsb2coU2FsZVByaWNlKScpDQpmb3IgKGZlYXQgaW4geF9sc3QpIHsNCiAgcGxvdF9zY2F0X3BhaXJzKGRmID0gdmFsX3RyYWluX1h5LCB4ID0gZmVhdCwgeV9sc3QgPSB5X2xzdCkNCn0NCmBgYA0KDQpUcmFuc2Zvcm1pbmcgYW5kIFdpbnNvcml6aW5nIGRvZXNuJ3QgaGVscCB0aGUgb3ZlcmFsbCBjb3JyZWxhdGlvbiB0byBTYWxlUHJpY2UuIExldCdzIGZhY2V0IGl0IG91dCBhbmQgc2VlLg0KDQojIyMgR2FyYWdlIEFyZWEgYW5kIENhcnMgYnkgVHlwZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpkZiA9IGZpbHRlcigNCiAgdmFsX3RyYWluX1h5LA0KICAoR2FyYWdlVHlwZSAlaW4lIGMoJ0F0dGNoZCcsICdEZXRjaGQnLCAnQnVpbHRJbicpKSAmDQogICAgKEdhcmFnZUNhcnMgIT0gNCkgIyBPbnkgb25lIHBvaW50IGluIHRoaXMgc3Vic2V0IHdpdGggNCBhbmQgaXQgZG9lc24ndCByZWFsbHkgY2hhbmdlIHRoZSBjb3JyZWxhdGlvbnMsIGJ1dCBpdCBzYXZlcyB2aXogc3BhY2UuDQopDQoNCmdncGxvdChkZiwgYWVzKHggPSBgV2luKHNxcnQoR2FyYWdlQXJlYSkpYCwgeSA9IGBsb2coU2FsZVByaWNlKWApKSArDQogIGdlb21faml0dGVyKGFscGhhID0gMC41KSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICdsbScpICsNCiAgZmFjZXRfZ3JpZChyb3dzID0gdmFycyhHYXJhZ2VUeXBlKSwgY29scyA9IHZhcnMoR2FyYWdlQ2Fycy5mYWN0KSkNCg0Kc3VtX2RmMCA9IGRmICU+JQ0KICBzdW1tYXJpemUoDQogICAgbiA9IG4oKSwNCiAgICBHYXJhZ2VBcmVhX2NvciA9IGNvcigNCiAgICAgIHggPSAuZGF0YSRgV2luKGxvZyhTYWxlUHJpY2UpKWAsDQogICAgICB5ID0gLmRhdGEkYEdhcmFnZUFyZWFgDQogICAgKSwNCiAgICBzcXJ0QXJlYV9jb3IgPSBjb3IoDQogICAgICB4ID0gLmRhdGEkYFdpbihsb2coU2FsZVByaWNlKSlgLA0KICAgICAgeSA9IC5kYXRhJGBzcXJ0KEdhcmFnZUFyZWEpYA0KICAgICksDQogICAgV2luc3FydEFyZWFfY29yID0gY29yKA0KICAgICAgeCA9IC5kYXRhJGBXaW4obG9nKFNhbGVQcmljZSkpYCwNCiAgICAgIHkgPSAuZGF0YSRgV2luKHNxcnQoR2FyYWdlQXJlYSkpYA0KICAgICkNCiAgKQ0KDQptaW5fbiA9IDMwDQoNCiMgc3VtX2RmMSA9IGRmICU+JQ0KIyAgIGdyb3VwX2J5KEdhcmFnZUNhcnMuZmFjdCwgR2FyYWdlVHlwZSkgJT4lDQojICAgc3VtbWFyaXplKA0KIyAgICAgbiA9IG4oKSwNCiMgICAgIEdhcmFnZUFyZWFfY29yID0gY29yKA0KIyAgICAgICB4ID0gLmRhdGEkYFdpbihsb2coU2FsZVByaWNlKSlgLA0KIyAgICAgICB5ID0gLmRhdGEkYEdhcmFnZUFyZWFgDQojICAgICApLA0KIyAgICAgc3FydEFyZWFfY29yID0gY29yKA0KIyAgICAgICB4ID0gLmRhdGEkYFdpbihsb2coU2FsZVByaWNlKSlgLA0KIyAgICAgICB5ID0gLmRhdGEkYHNxcnQoR2FyYWdlQXJlYSlgDQojICAgICApLA0KIyAgICAgV2luc3FydEFyZWFfY29yID0gY29yKA0KIyAgICAgICB4ID0gLmRhdGEkYFdpbihsb2coU2FsZVByaWNlKSlgLA0KIyAgICAgICB5ID0gLmRhdGEkYFdpbihzcXJ0KEdhcmFnZUFyZWEpKWANCiMgICAgICkNCiMgICApICU+JQ0KIyAgIGZpbHRlcihuID49IG1pbl9uKQ0KDQpzdW1fZGYyID0gZGYgJT4lIGdyb3VwX2J5KEdhcmFnZVR5cGUpICU+JQ0KICBzdW1tYXJpemUoDQogICAgbiA9IG4oKSwNCiAgICBHYXJhZ2VBcmVhX2NvciA9IGNvcigNCiAgICAgIHggPSAuZGF0YSRgV2luKGxvZyhTYWxlUHJpY2UpKWAsDQogICAgICB5ID0gLmRhdGEkYEdhcmFnZUFyZWFgDQogICAgKSwNCiAgICBzcXJ0QXJlYV9jb3IgPSBjb3IoDQogICAgICB4ID0gLmRhdGEkYFdpbihsb2coU2FsZVByaWNlKSlgLA0KICAgICAgeSA9IC5kYXRhJGBzcXJ0KEdhcmFnZUFyZWEpYA0KICAgICksDQogICAgV2luc3FydEFyZWFfY29yID0gY29yKA0KICAgICAgeCA9IC5kYXRhJGBXaW4obG9nKFNhbGVQcmljZSkpYCwNCiAgICAgIHkgPSAuZGF0YSRgV2luKHNxcnQoR2FyYWdlQXJlYSkpYA0KICAgICkNCiAgKSAlPiUNCiAgZmlsdGVyKG4gPj0gbWluX24pDQoNCnN1bV9kZjMgPSBkZiAlPiUgZ3JvdXBfYnkoR2FyYWdlQ2Fycy5mYWN0KSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIG4gPSBuKCksDQogICAgR2FyYWdlQXJlYV9jb3IgPSBjb3IoDQogICAgICB4ID0gLmRhdGEkYFdpbihsb2coU2FsZVByaWNlKSlgLA0KICAgICAgeSA9IC5kYXRhJGBHYXJhZ2VBcmVhYA0KICAgICksDQogICAgc3FydEFyZWFfY29yID0gY29yKA0KICAgICAgeCA9IC5kYXRhJGBXaW4obG9nKFNhbGVQcmljZSkpYCwNCiAgICAgIHkgPSAuZGF0YSRgc3FydChHYXJhZ2VBcmVhKWANCiAgICApLA0KICAgIFdpbnNxcnRBcmVhX2NvciA9IGNvcigNCiAgICAgIHggPSAuZGF0YSRgV2luKGxvZyhTYWxlUHJpY2UpKWAsDQogICAgICB5ID0gLmRhdGEkYFdpbihzcXJ0KEdhcmFnZUFyZWEpKWANCiAgICApDQogICkgJT4lDQogIGZpbHRlcihuID49IG1pbl9uKQ0KDQpzdW1fZGYwICU+JQ0KICBtZXJnZSh5ID0gc3VtX2RmMiwgYWxsID0gVCkgJT4lDQogIG1lcmdlKHkgPSBzdW1fZGYzLCBhbGwgPSBUKSAlPiUNCiAgIyBtZXJnZSh5ID0gc3VtX2RmMSwgYWxsID0gVCkgJT4lDQogIHNlbGVjdCgNCiAgICBjKCdHYXJhZ2VUeXBlJywgJ0dhcmFnZUNhcnMuZmFjdCcsICduJywgJ0dhcmFnZUFyZWFfY29yJywNCiAgICAgICdzcXJ0QXJlYV9jb3InLCAnV2luc3FydEFyZWFfY29yJykNCiAgKSAlPiUNCiAgYXJyYW5nZShHYXJhZ2VUeXBlLCBHYXJhZ2VDYXJzLmZhY3QpDQpgYGANCg0KVHJhbnNmb3JtaW5nIGFuZCBXaW5zb3JpemluZyBHYXJhZ2VBcmVhIGltcHJvdmVzIGNvcnJlbGF0aW9uIHRvIHRoZSB0YXJnZXQgdmFyaWFibGUgKCJXaW4obG9nKFNhbGVQcmljZSkpIikgc29tZXdoYXQgd2hlbiBleGNsdWRpbmcgaG91c2VzIHdpdGhvdXQgZ2FyYWdlcy4gR3JvdXBpbmcgYnkgZ2FyYWdlIHR5cGUgaGVscHMgaW4gc29tZSBjYXNlcy4NCg0KV2hldGhlciB0byB0cmFuc2Zvcm0gdGhlIHZhcmlhYmxlIG9yIG5vdCBtYXkgZGVwZW5kIG9uIHdoaWNoIE1MIGFsZ29yaXRobSB3ZSdyZSB1c2luZyBhbmQgaG93LiBBIGRlY2lzaW9uIHRyZWUgd2lsbCBsaWtlbHkgYmUgaW5kaWZmZXJlbnQgdG8gdHJhbmZvcm1hdGlvbnMsIHRob3VnaCBpdCBtYXkgYmVuZWZpdCBmcm9tIG5vaXNlIHJlZHVjdGlvbiB3aXRoIFdpbnNvcml6YXRpb24uIEEgbGluZWFyIHJlZ3Jlc3Npb24gd2lsbCBvbmx5IGJlbmVmaXQgaWYgdHlwZSBhbmQvb3IgbWlzc2luZ25lc3MgaXMgZmFjdG9yZWQgaW4sIGFzIHdvdWxkIEtOTi4NCg0KR3JvdXBpbmcgYnkgbnVtYmVyIG9mIGNhcnMgbG93ZXJzIHRoZSBhcmVhOnByaWNlIGNvcnJlbGF0aW9uIHRvIG5vIGNvcnJlbGF0aW9uLiBBbmQsIGluIGZhY3QsIG51bWJlciBvZiBjYXJzIGhhcyBhIHN0cm9uZ2VyIGNvcnJlbGF0aW9uIHRvIHRoZSB0YXJnZXQgdmFyaWFibGUgdGhhbiBhcmVhIGRvZXMuIExldCdzIHNlZSBob3cgZ3JvdXBpbmcgYnkgdHlwZSBmdXJ0aGVyIGltcHJvdmVzIHRoYXQgY29ycmVsYXRpb24uDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmRmID0gZmlsdGVyKA0KICB2YWxfdHJhaW5fWHksDQogIChHYXJhZ2VUeXBlICVpbiUgYygnQXR0Y2hkJywgJ0RldGNoZCcsICdCdWlsdEluJykpDQopDQoNCmdncGxvdChkZiwgYWVzKHggPSBHYXJhZ2VDYXJzLCB5ID0gYGxvZyhTYWxlUHJpY2UpYCkpICsNCiAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xtJykgKw0KICBmYWNldF93cmFwKGZhY2V0cyA9IHZhcnMoR2FyYWdlVHlwZSkpDQoNCnN1bV9kZjAgPSBkZiAlPiUNCiAgc3VtbWFyaXplKA0KICAgIG4gPSBuKCksDQogICAgQ2Fyc19jb3IgPSBjb3IoDQogICAgICB4ID0gLmRhdGEkYFdpbihsb2coU2FsZVByaWNlKSlgLA0KICAgICAgeSA9IC5kYXRhJEdhcmFnZUNhcnMNCiAgICApLA0KICAgIFdpbkNhcnNfY29yID0gY29yKA0KICAgICAgeCA9IC5kYXRhJGBXaW4obG9nKFNhbGVQcmljZSkpYCwNCiAgICAgIHkgPSAuZGF0YSRgV2luKEdhcmFnZUNhcnMpYA0KICAgICksDQogICAgV2luc3FydEFyZWFfY29yID0gY29yKA0KICAgICAgeCA9IC5kYXRhJGBXaW4obG9nKFNhbGVQcmljZSkpYCwNCiAgICAgIHkgPSAuZGF0YSRgV2luKHNxcnQoR2FyYWdlQXJlYSkpYA0KICAgICkNCiAgKQ0KDQpzdW1fZGYyID0gZGYgJT4lIGdyb3VwX2J5KEdhcmFnZVR5cGUpICU+JQ0KICBzdW1tYXJpemUoDQogICAgbiA9IG4oKSwNCiAgICBDYXJzX2NvciA9IGNvcigNCiAgICAgIHggPSAuZGF0YSRgV2luKGxvZyhTYWxlUHJpY2UpKWAsDQogICAgICB5ID0gLmRhdGEkR2FyYWdlQ2Fycw0KICAgICksDQogICAgV2luQ2Fyc19jb3IgPSBjb3IoDQogICAgICB4ID0gLmRhdGEkYFdpbihsb2coU2FsZVByaWNlKSlgLA0KICAgICAgeSA9IC5kYXRhJGBXaW4oR2FyYWdlQ2FycylgDQogICAgKSwNCiAgICBXaW5zcXJ0QXJlYV9jb3IgPSBjb3IoDQogICAgICB4ID0gLmRhdGEkYFdpbihsb2coU2FsZVByaWNlKSlgLA0KICAgICAgeSA9IC5kYXRhJGBXaW4oc3FydChHYXJhZ2VBcmVhKSlgDQogICAgKQ0KICApDQoNCnN1bV9kZjAgJT4lDQogIG1lcmdlKHkgPSBzdW1fZGYyLCBhbGwgPSBUKSAlPiUNCiAgc2VsZWN0KA0KICAgIGMoJ0dhcmFnZVR5cGUnLCAnbicsICdDYXJzX2NvcicsICdXaW5DYXJzX2NvcicsICdXaW5zcXJ0QXJlYV9jb3InKQ0KICApICU+JQ0KICBhcnJhbmdlKEdhcmFnZVR5cGUpDQpgYGANCg0KSXQgbG9va3Mgd29ydGggZHJvcHBpbmcgR2FyYWdlQXJlYSBpbiBmYXZvciBvZiBHYXJhZ2VDYXJzLiBUaGVyZSBpcyBhbiBhcmd1bWVudCBhZ2FpbnN0IGxpbmVhciByZWdyZXNzaW9uIHVzaW5nIGRpc2NyZXRlIHZhcmlhYmxlcywgYnV0IGl0IHNlZW1zIHRvIHdvcmsgbm9uZXRoZWxlc3MuDQoNCldpbnNvcml6aW5nIEdhcmFnZUNhcnMgb25seSBwcm9kdWNlcyBhIG1hcmdpbmFsIGJlbmVmaXQgd2hpY2ggbWF5IHByb3ZlIHNwdXJpb3VzIGFzIGl0IG9ubHkgYWRqdXN0cyBvbmUgcG9pbnQuIFBsdXMsIGl0IHJlZHVjZXMgbm9ybWFsaXR5LiBTbywgSSdsbCBqdXN0IHVzZSB0aGUgcmF3IGZlYXR1cmUuDQoNClRoZXJlIGFyZSBsaWtlbHkgb3RoZXIgZmVhdHVyZXMgYXQgcGxheSwgbGlrZSB5ZWFyIGJ1aWx0LCB0aGF0IGFsc28gYWZmZWN0IHRoaW5ncyBsaWtlIHRoZSBkaWZmZXJlbmNlIGluIHRoZSBwcmljZXMgb2YgdHdvLWNhciBhdHRhY2hlZCBnYXJhZ2VzIGFuZCB0d28tY2FyIGRldGFjaGVkIGdhcmFnZXMuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmZlbmNlZF9qYnYoDQogIGRhdGEgPSBkZltkZiRHYXJhZ2VDYXJzICE9IDQsIF0sDQogIHggPSAnR2FyYWdlVHlwZScsDQogIHkgPSAnbG9nKFNhbGVQcmljZSknLA0KICBqaXRfY29sID0gJ1llYXJCdWlsdC5mYWN0JywNCiAgbGVnX2xibCA9ICdZZWFyIEJ1aWx0Jw0KKSArDQogIGZhY2V0X3dyYXAoZmFjZXRzID0gdmFycyhHYXJhZ2VDYXJzLmZhY3QpKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHNlbGVjdCgNCiAgdmFsX3RyYWluX1h5LA0KICAtYygnR2FyYWdlQ2Fycy5mYWN0JywgJ0dhcmFnZUNhcnMucGx1czEnLCAnR2FyYWdlQ2Fycy5iaW4nLA0KICAgICAnR2FyYWdlQ2Fycy5iaW4uZmFjdCcsICdHYXJhZ2VBcmVhJywgJ3NxcnQoR2FyYWdlQXJlYSknLA0KICAgICAnV2luKHNxcnQoR2FyYWdlQXJlYSkpJykNCikNCmBgYA0KDQojIyBHYXJhZ2VRdWFsLCBDb25kDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnN1bW1hcnkodmFsX3RyYWluX1h5WyAsIGMoJ0dhcmFnZVF1YWwnLCAnR2FyYWdlQ29uZCcpXSkNCnZhbF90cmFpbl9YeSA9IHNlbGVjdCh2YWxfdHJhaW5fWHksIC1jKCdHYXJhZ2VRdWFsJywgJ0dhcmFnZUNvbmQnKSkNCmBgYA0KDQojIyBQYXZlZERyaXZlDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnN1bW1hcnkodmFsX3RyYWluX1h5JFBhdmVkRHJpdmUpDQp2YWxfdHJhaW5fWHkgPSBzZWxlY3QodmFsX3RyYWluX1h5LCAtYygnUGF2ZWREcml2ZScpKQ0KYGBgDQoNCiMjIFdvb2REZWNrU0YNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KIyMjIE5vcm1hbGl6ZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ1dvb2REZWNrU0YnDQpzdW1tYXJ5KHZhbF90cmFpbl9YeVt4XSkNCnN1bV9hbmRfdHJhbnNfY29udCgNCiAgZGF0YSA9IHZhbF90cmFpbl9YeVt2YWxfdHJhaW5fWHkkV29vZERlY2tTRiAhPSAwLCBdLA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAxMCwNCiAgdF9iaW53ID0gLjENCikNCmBgYA0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ2NicnQoV29vZERlY2tTRiknDQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgnY2JydChXb29kRGVja1NGKScgPSAoV29vZERlY2tTRileKDEvMykpDQoNCiMgUmVjYWxjdWxhdGUgYmVzdCBub3JtYWxpemVycy4NCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCmJlc3Rfbm9ybWFsaXplcnMgPSBmaW5kX2Jlc3Rfbm9ybWFsaXplcl9wZXJfZmVhdCgNCiAgZGYgPSB2YWxfdHJhaW5fWHksDQogIGZlYXRzX2xzdCA9IG51bV9mZWF0cywNCiAgZnVuY3NfbHN0ID0gZnVuY3NfbHN0LA0KICBleGNsdWRlX3ZhbHMgPSBsaXN0KDApDQopDQoNCnN1bW1hcnkodmFsX3RyYWluX1h5W3hdKQ0Kc3VtX2FuZF90cmFuc19jb250KA0KICBkYXRhID0gdmFsX3RyYWluX1h5W3ZhbF90cmFpbl9YeVtbeF1dICE9IDAsIF0sDQogIHggPSB4LA0KICBmdW5jID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRmdW5jLA0KICBmdW5jX25hbWUgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJG5hbWUsDQogIHhfYmludyA9IC4xLA0KICB0X2JpbncgPSAuMQ0KKQ0KYGBgDQoNCiMjIyBXaW5zb3JpemUNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGYgPSBmaWx0ZXIodmFsX3RyYWluX1h5LCBXb29kRGVja1NGICE9IDApDQoNCnFxbm9ybSh5ID0gZGYkV29vZERlY2tTRiwgeWxhYiA9ICdXb29kRGVja1NGJykNCnFxbGluZSh5ID0gZGYkV29vZERlY2tTRiwgeWxhYiA9ICdXb29kRGVja1NGJykNCg0KcXFub3JtKHkgPSBkZltbeF1dLCB5bGFiID0geCkNCnFxbGluZSh5ID0gZGZbW3hdXSwgeWxhYiA9IHgpDQoNCldpbl9jYnJ0X3ggPSBXaW5zb3JpemUoDQogIHggPSBkZltbeF1dLA0KICBwcm9icyA9IGMoMC4wMSwgMC45OSksDQogIG5hLnJtID0gVA0KKQ0KDQpxcW5vcm0oeSA9IFdpbl9jYnJ0X3gsIHlsYWIgPSB4KQ0KcXFsaW5lKHkgPSBXaW5fY2JydF94LCB5bGFiID0geCkNCg0KV2luX3Jhd194ID0gV2luc29yaXplKA0KICB4ID0gZGYkV29vZERlY2tTRiwNCiAgcHJvYnMgPSBjKDAsIDAuOTUpLA0KICBuYS5ybSA9IFQNCikNCg0KcXFub3JtKHkgPSBXaW5fcmF3X3gsIHlsYWIgPSAnV29vZERlY2tTRicpDQpxcWxpbmUoeSA9IFdpbl9yYXdfeCwgeWxhYiA9ICdXb29kRGVja1NGJykNCg0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBkZiRXb29kRGVja1NGKSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gZGZbW3hdXSkpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IFdpbl9jYnJ0X3gpKQ0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBXaW5fcmF3X3gpKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKA0KICAgICdXaW4oY2JydChXb29kRGVja1NGKSknID0gaWZlbHNlKA0KICAgICAgV29vZERlY2tTRiA9PSAwLA0KICAgICAgMCwNCiAgICAgIFdpbnNvcml6ZSgNCiAgICAgICAgV29vZERlY2tTRl4oMS8zKSwNCiAgICAgICAgbWludmFsID0gbWluKFdpbl9jYnJ0X3gpLA0KICAgICAgICBtYXh2YWwgPSBtYXgoV2luX2NicnRfeCkNCiAgICAgICkNCiAgICApDQogICkgJT4lDQogIG11dGF0ZSgNCiAgICAnV2luKFdvb2REZWNrU0YpJyA9IGlmZWxzZSgNCiAgICAgIFdvb2REZWNrU0YgPT0gMCwNCiAgICAgIDAsDQogICAgICBXaW5zb3JpemUoDQogICAgICAgIFdvb2REZWNrU0YsDQogICAgICAgIG1pbnZhbCA9IG1pbihXaW5fcmF3X3gpLA0KICAgICAgICBtYXh2YWwgPSBtYXgoV2luX3Jhd194KQ0KICAgICAgKQ0KICAgICkNCiAgKQ0KYGBgDQoNCiMjIyBCaW5hcml6ZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgnV29vZERlY2suYmluJyA9IGlmZWxzZShXb29kRGVja1NGID09IDAsIDAsIDEpKSAlPiUNCiAgbXV0YXRlKCdXb29kRGVjay5iaW4uZmFjdCcgPSBmYWN0b3IoV29vZERlY2suYmluLCBvcmRlcmVkID0gVCkpDQoNCnggPSAnV29vZERlY2suYmluLmZhY3QnDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5LCBtaW5fbiA9IDMwKQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQpgYGANCg0KIyMjIENvcnJlbGF0aW9ucw0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpudW1fZmVhdHMgPSBjb2xuYW1lcyhzZWxlY3QodmFsX3RyYWluX1h5LCB3aGVyZShpcy5udW1lcmljKSkpDQp4X2xzdCA9IGMoJ1dvb2REZWNrU0YnLCAnY2JydChXb29kRGVja1NGKScsICdXaW4oY2JydChXb29kRGVja1NGKSknLA0KICAgICAgICAgICdXaW4oV29vZERlY2tTRiknLCAnV29vZERlY2suYmluJykNCnggPSAnV2luKGNicnQoV29vZERlY2tTRikpJw0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAhaXMubmEoLmRhdGFbW3hdXSkNCiAgKSwNCiAgeF9sc3QgPSB4X2xzdCwNCiAgZmVhdHMgPSBudW1fZmVhdHMNCikNCmRmDQpwcmludCgiU3VtbWFyeSBvZiBhYnNvbHV0ZSB2YWx1ZXMgb2YgUGVhcnNvbidzIFJzOiIpDQpkZiA9IGFicyhkZikNCnN1bW1hcnkoYWJzKGRmKSkNCg0KZGYgPSBtZWx0KGRmKQ0KZ2dwbG90KGRmLCBhZXMoeCA9IHZhcmlhYmxlLCB5ID0gdmFsdWUpKSArDQogIGdlb21fYm94cGxvdChub3RjaCA9IFQpICsNCiAgeWxhYihsYWJlbCA9ICdBYnNvbHV0ZSBWYWx1ZSBvZiBDb3JyZWxhdGlvbiB0byBPdGhlciBGZWF0dXJlcycpDQoNCmRmID0gZ2V0X2NvcnMoDQogIGRhdGEgPSBmaWx0ZXIoDQogICAgc2VsZWN0KHZhbF90cmFpbl9YeSwgYWxsX29mKG51bV9mZWF0cykpLA0KICAgIC5kYXRhW1t4XV0gIT0gMA0KICApLA0KICB4X2xzdCA9IHhfbHN0LA0KICBmZWF0cyA9IG51bV9mZWF0cw0KKQ0KZGYNCnByaW50KCJTdW1tYXJ5IG9mIGFic29sdXRlIHZhbHVlcyBvZiBQZWFyc29uJ3MgUnMgKG5vIDBzKToiKQ0KZGYgPSBhYnMoZGYpDQpzdW1tYXJ5KGFicyhkZikpDQoNCmRmID0gbWVsdChkZikNCmdncGxvdChkZiwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHZhbHVlKSkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArDQogIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMgKG5vIDBzKScpDQoNCnlfbHN0ID0gYygnV2luKGxvZyhTYWxlUHJpY2UpKScpDQpmb3IgKGZlYXQgaW4geF9sc3QpIHsNCiAgcGxvdF9zY2F0X3BhaXJzKGRmID0gdmFsX3RyYWluX1h5LCB4ID0gZmVhdCwgeV9sc3QgPSB5X2xzdCkNCiAgIyBwbG90X3NjYXRfcGFpcnMoDQogICMgICBkZiA9IGZpbHRlcih2YWxfdHJhaW5fWHksIFdvb2REZWNrU0YgIT0gMCksDQogICMgICB4ID0gZmVhdCwNCiAgIyAgIHlfbHN0ID0geV9sc3QNCiAgIyApDQp9DQpgYGANCg0KVGhlcmUncyBubyByZWFsIGNvcnJlbGF0aW9uIGJldHdlZW4gZGVjayBzcXVhcmUgZm9vdGFnZSBhbmQgdGhlIHRhcmdldCBwcmljZSB3aGVuIHlvdSByZW1vdmUgaG91c2VzIHdpdGhvdXQgZGVja3M7IHRoZSBiaW5hcnkgdmVyc2lvbiBkb2VzIG1vc3Qgb2YgdGhlIHdvcmsuIEJ1dCwgdGhlIHRyYW5zZm9ybWF0aW9uIGRvZXMgaXQgYSBsaXR0bGUgYmV0dGVyIGFuZCBkb2VzIG9mZmVyIG1vcmUgbm9ybWFsaXplZCBkaXN0YW5jZSBiZXR3ZWVuIHBvaW50cyBmb3IgS05OLg0KDQojIyMgSGFyZCBDb2RlDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnV2luKGNicnQoV29vZERlY2tTRikpJw0KDQptaW5fdmFsID0gbWluKFdpbl9jYnJ0X3gpDQptYXhfdmFsID0gbWF4KFdpbl9jYnJ0X3gpDQpwcmludChwYXN0ZSgibWluX3ZhbDoiLCBtaW5fdmFsKSkNCnByaW50KHBhc3RlKCJtYXhfdmFsOiIsIG1heF92YWwpKQ0KDQojIEFscmVhZHkgaGFyZCBjb2RlZCBhYm92ZQ0KDQp2YWxfdHJhaW5fWHkgPSBzZWxlY3QoDQogIHZhbF90cmFpbl9YeSwNCiAgLWMoJ1dvb2REZWNrLmJpbicsICdXb29kRGVjay5iaW4uZmFjdCcsICdXaW4oV29vZERlY2tTRiknKQ0KKQ0KDQpnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IC5kYXRhW1t4XV0pKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gLjI1KQ0KYGBgDQoNCiMjIE9wZW5Qb3JjaFNGIA0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQojIyMgTm9ybWFsaXplDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnT3BlblBvcmNoU0YnDQpzdW1tYXJ5KHZhbF90cmFpbl9YeVt4XSkNCnN1bV9hbmRfdHJhbnNfY29udCgNCiAgZGF0YSA9IHZhbF90cmFpbl9YeVt2YWxfdHJhaW5fWHkkT3BlblBvcmNoU0YgIT0gMCwgXSwNCiAgeCA9IHgsDQogIGZ1bmMgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJGZ1bmMsDQogIGZ1bmNfbmFtZSA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkbmFtZSwNCiAgeF9iaW53ID0gMTAsDQogIHRfYmludyA9IDAuMQ0KKQ0KYGBgDQoNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdjYnJ0KE9wZW5Qb3JjaFNGKScNCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKCdjYnJ0KE9wZW5Qb3JjaFNGKScgPSBPcGVuUG9yY2hTRl4oMS8zKSkNCg0KIyBSZWNhbGN1bGF0ZSBiZXN0IG5vcm1hbGl6ZXJzLg0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KYmVzdF9ub3JtYWxpemVycyA9IGZpbmRfYmVzdF9ub3JtYWxpemVyX3Blcl9mZWF0KA0KICBkZiA9IHZhbF90cmFpbl9YeSwNCiAgZmVhdHNfbHN0ID0gbnVtX2ZlYXRzLA0KICBmdW5jc19sc3QgPSBmdW5jc19sc3QsDQogIGV4Y2x1ZGVfdmFscyA9IGxpc3QoMCkNCikNCg0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbeF0pDQpzdW1fYW5kX3RyYW5zX2NvbnQoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHlbdmFsX3RyYWluX1h5W1t4XV0gIT0gMCwgXSwNCiAgeCA9IHgsDQogIGZ1bmMgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJGZ1bmMsDQogIGZ1bmNfbmFtZSA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkbmFtZSwNCiAgeF9iaW53ID0gMC4xLA0KICB0X2JpbncgPSAwLjENCikNCmBgYA0KDQojIyMgV2luc29yaXplDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmRmID0gZmlsdGVyKHZhbF90cmFpbl9YeSwgT3BlblBvcmNoU0YgIT0gMCkNCnggPSAnY2JydChPcGVuUG9yY2hTRiknDQoNCnFxbm9ybSh5ID0gZGYkT3BlblBvcmNoU0YsIHlsYWIgPSAnT3BlblBvcmNoU0YnKQ0KcXFsaW5lKHkgPSBkZiRPcGVuUG9yY2hTRiwgeWxhYiA9ICdPcGVuUG9yY2hTRicpDQoNCnFxbm9ybSh5ID0gZGZbW3hdXSwgeWxhYiA9IHgpDQpxcWxpbmUoeSA9IGRmW1t4XV0sIHlsYWIgPSB4KQ0KDQpXaW5fY2JydF94ID0gV2luc29yaXplKA0KICB4ID0gZGZbW3hdXSwNCiAgcHJvYnMgPSBjKDAuMDAxLCAwLjk5NCksDQogIG5hLnJtID0gVA0KKQ0KDQpxcW5vcm0oeSA9IFdpbl9jYnJ0X3gsIHlsYWIgPSB4KQ0KcXFsaW5lKHkgPSBXaW5fY2JydF94LCB5bGFiID0geCkNCg0KV2luX3Jhd194ID0gV2luc29yaXplKA0KICB4ID0gZGYkT3BlblBvcmNoU0YsDQogIHByb2JzID0gYygwLCAwLjk1KSwNCiAgbmEucm0gPSBUDQopDQoNCnFxbm9ybSh5ID0gV2luX3Jhd194LCB5bGFiID0gJ09wZW5Qb3JjaFNGJykNCnFxbGluZSh5ID0gV2luX3Jhd194LCB5bGFiID0gJ09wZW5Qb3JjaFNGJykNCg0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBkZiRPcGVuUG9yY2hTRikpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IGRmW1t4XV0pKQ0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBXaW5fY2JydF94KSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gV2luX3Jhd194KSkNCmBgYA0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoDQogICAgJ1dpbihjYnJ0KE9wZW5Qb3JjaFNGKSknID0gaWZlbHNlKA0KICAgICAgT3BlblBvcmNoU0YgPT0gMCwNCiAgICAgIDAsDQogICAgICBXaW5zb3JpemUoDQogICAgICAgIE9wZW5Qb3JjaFNGXigxLzMpLA0KICAgICAgICAjIHByb2JzID0gYygwLjAwMSwgMC45OTQpLA0KICAgICAgICAjIG5hLnJtID0gVA0KICAgICAgICBtaW52YWwgPSBtaW4oV2luX2NicnRfeCksDQogICAgICAgIG1heHZhbCA9IG1heChXaW5fY2JydF94KQ0KICAgICAgKQ0KICAgICkNCiAgKQ0KYGBgDQoNCkxvb2tzIGxpa2Ugc29tZSBwb2x5bW9kYWxpdHkgaGFwcGVuaW5nLiBZZWFyIGJ1aWx0IGRvZXNuJ3Qgc2VlbSB0byBleHBsYWluIGl0LiBJJ20gbm90IGdvaW5nIHRvIG1hbnVhbGx5IHNlYXJjaCBmb3IgaXQgYW55IGZ1cnRoZXIsIGJ1dCBhIGRlY2lzaW9uIHRyZWUgb3Igb3RoZXIgTUwgYWxnb3JpdGhtIG1heSBmaW5kIGFuZCAiZmFjdG9yIiBpbiB0aGUgaGlkZGVuIGludGVyYWN0aW9uLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnZ3Bsb3QoDQogIGZpbHRlcih2YWxfdHJhaW5fWHksIE9wZW5Qb3JjaFNGICE9IDApLA0KICBhZXMoeCA9IGBjYnJ0KE9wZW5Qb3JjaFNGKWApDQopICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjEpICsNCiAgZmFjZXRfd3JhcChmYWNldHMgPSB2YXJzKFllYXJCdWlsdC5mYWN0KSwgbmNvbCA9IDEpDQoNCmdncGxvdCgNCiAgZmlsdGVyKHZhbF90cmFpbl9YeSwgT3BlblBvcmNoU0YgIT0gMCksDQogIGFlcyh4ID0gYGNicnQoT3BlblBvcmNoU0YpYCwgeSA9IGBsb2coU2FsZVByaWNlKWApDQopICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBZZWFyQnVpbHQpKQ0KYGBgDQoNCiMjIyBCaW5hcml6ZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgnT3BlblBvcmNoLmJpbicgPSBpZmVsc2UoT3BlblBvcmNoU0YgPT0gMCwgMCwgMSkpICU+JQ0KICBtdXRhdGUoJ09wZW5Qb3JjaC5iaW4uZmFjdCcgPSBmYWN0b3IoT3BlblBvcmNoLmJpbiwgb3JkZXJlZCA9IFQpKQ0KDQp4ID0gJ09wZW5Qb3JjaC5iaW4uZmFjdCcNCnkgPSAnU2FsZVByaWNlJw0Kc3VtbWFyaXplX2J5KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCnkgPSAnbG9nKFNhbGVQcmljZSknDQpzdW1fYW5kX3RyYW5zX2ZhY3QoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KDQpwX3ZhbHMgPSBnZXRfc2lnbmlmX2xldmVscyhkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeiA9IHksIG1pbl9uID0gMzApDQoNCmhlYXRtYXAuMigNCiAgICB4ID0gYXMubWF0cml4KHBfdmFscyRwdmFsX2RmKSwNCiAgICBzY2FsZSA9ICdub25lJywNCiAgICBSb3d2ID0gRiwNCiAgICBDb2x2ID0gRiwNCiAgICBkZW5kcm9ncmFtID0gJ25vbmUnLA0KICAgIGNlbGxub3RlID0gZm9ybWF0KHBfdmFscyRwdmFsX2RmLCBkaWdpdHMgPSAyKSwNCiAgICBub3RlY2V4ID0gMC43NSwNCiAgICBub3RlY29sID0gJ2JsYWNrJywNCiAgICBtYWluID0gcGFzdGUoeSwgJ3AtdmFsdWVzJyksDQogICAga2V5ID0gRg0KICApDQoNCnByaW50KA0KICAgIHBhc3RlKA0KICAgICAgIkxldmVscyB3LyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCIsDQogICAgICB5LA0KICAgICAgInRoYW4gYW5vdGhlciBsZXZlbDoiDQogICAgKQ0KICApDQpwcmludChwX3ZhbHMkc2lnbmlmX2xldmVscykNCmBgYA0KDQojIyMgQ29ycmVsYXRpb25zDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm51bV9mZWF0cyA9IGNvbG5hbWVzKHNlbGVjdCh2YWxfdHJhaW5fWHksIHdoZXJlKGlzLm51bWVyaWMpKSkNCnhfbHN0ID0gYygnT3BlblBvcmNoU0YnLCAnY2JydChPcGVuUG9yY2hTRiknLCAnV2luKGNicnQoT3BlblBvcmNoU0YpKScsDQogICAgICAgICAgJ09wZW5Qb3JjaC5iaW4nKQ0KeCA9ICdXaW4oY2JydChPcGVuUG9yY2hTRikpJw0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAhaXMubmEoLmRhdGFbW3hdXSkNCiAgKSwNCiAgeF9sc3QgPSB4X2xzdCwNCiAgZmVhdHMgPSBudW1fZmVhdHMNCikNCmRmDQpwcmludCgiU3VtbWFyeSBvZiBhYnNvbHV0ZSB2YWx1ZXMgb2YgUGVhcnNvbidzIFJzOiIpDQpkZiA9IGFicyhkZikNCnN1bW1hcnkoYWJzKGRmKSkNCg0KZGYgPSBtZWx0KGRmKQ0KZ2dwbG90KGRmLCBhZXMoeCA9IHZhcmlhYmxlLCB5ID0gdmFsdWUpKSArDQogIGdlb21fYm94cGxvdChub3RjaCA9IFQpICsNCiAgeWxhYihsYWJlbCA9ICdBYnNvbHV0ZSBWYWx1ZSBvZiBDb3JyZWxhdGlvbiB0byBPdGhlciBGZWF0dXJlcycpDQoNCmRmID0gZ2V0X2NvcnMoDQogIGRhdGEgPSBmaWx0ZXIoDQogICAgc2VsZWN0KHZhbF90cmFpbl9YeSwgYWxsX29mKG51bV9mZWF0cykpLA0KICAgIC5kYXRhW1t4XV0gIT0gMA0KICApLA0KICB4X2xzdCA9IHhfbHN0LA0KICBmZWF0cyA9IG51bV9mZWF0cw0KKQ0KZGYNCnByaW50KCJTdW1tYXJ5IG9mIGFic29sdXRlIHZhbHVlcyBvZiBQZWFyc29uJ3MgUnMgKG5vIDBzKToiKQ0KZGYgPSBhYnMoZGYpDQpzdW1tYXJ5KGFicyhkZikpDQoNCmRmID0gbWVsdChkZikNCmdncGxvdChkZiwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHZhbHVlKSkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArDQogIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMgKG5vIDBzKScpDQoNCnlfbHN0ID0gYygnV2luKGxvZyhTYWxlUHJpY2UpKScpDQpmb3IgKGZlYXQgaW4geF9sc3QpIHsNCiAgcGxvdF9zY2F0X3BhaXJzKGRmID0gdmFsX3RyYWluX1h5LCB4ID0gZmVhdCwgeV9sc3QgPSB5X2xzdCkNCiAgcGxvdF9zY2F0X3BhaXJzKA0KICAgIGRmID0gdmFsX3RyYWluX1h5W3ZhbF90cmFpbl9YeVtbeF1dICE9IDAsIF0sDQogICAgeCA9IGZlYXQsDQogICAgeV9sc3QgPSB5X2xzdA0KICApDQp9DQpgYGANCg0KSW4gdGhpcyBjYXNlLCB0aGUgYmluYXJ5IHZhcmlhYmxlIGlzIGRvaW5nIGFsbCB0aGUgd29yay4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5ID0gc2VsZWN0KA0KICB2YWxfdHJhaW5fWHksDQogIC1jKCdjYnJ0KE9wZW5Qb3JjaFNGKScsICdXaW4oY2JydChPcGVuUG9yY2hTRikpJywgJ09wZW5Qb3JjaC5iaW4uZmFjdCcpDQopDQpgYGANCg0KIyMgRW5jbG9zZWRQb3JjaCANCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KIyMjIE5vcm1hbGl6ZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ0VuY2xvc2VkUG9yY2gnDQpzdW1tYXJ5KHZhbF90cmFpbl9YeVt4XSkNCnN1bV9hbmRfdHJhbnNfY29udCgNCiAgZGF0YSA9IHZhbF90cmFpbl9YeVt2YWxfdHJhaW5fWHkkRW5jbG9zZWRQb3JjaCAhPSAwLCBdLA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSA1LA0KICB0X2JpbncgPSA1DQopDQpgYGANCg0KIyMjIFdpbnNvcml6ZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpkZiA9IGZpbHRlcih2YWxfdHJhaW5fWHksIEVuY2xvc2VkUG9yY2ggIT0gMCkNCg0KcXFub3JtKHkgPSBkZltbeF1dLCB5bGFiID0geCkNCnFxbGluZSh5ID0gZGZbW3hdXSwgeWxhYiA9IHgpDQoNCldpbl9yYXdfeCA9IFdpbnNvcml6ZSgNCiAgeCA9IGRmJEVuY2xvc2VkUG9yY2gsDQogIHByb2JzID0gYygwLjAwMSwgMC45OTkpLA0KICBuYS5ybSA9IFQNCikNCg0KcXFub3JtKHkgPSBXaW5fcmF3X3gsIHlsYWIgPSAnV2luX3Jhd194JykNCnFxbGluZSh5ID0gV2luX3Jhd194LCB5bGFiID0gJ1dpbl9yYXdfeCcpDQoNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gZGZbW3hdXSkpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IFdpbl9yYXdfeCkpDQpgYGANCg0KDQojIyMgQmluYXJpemUNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoJ0VuY2xvc2VkUG9yY2guYmluJyA9IGlmZWxzZShFbmNsb3NlZFBvcmNoID09IDAsIDAsIDEpKSAlPiUNCiAgbXV0YXRlKA0KICAgICdFbmNsb3NlZFBvcmNoLmJpbi5mYWN0JyA9IGZhY3RvcigNCiAgICAgIEVuY2xvc2VkUG9yY2guYmluLA0KICAgICAgb3JkZXJlZCA9IFQNCiAgICApDQogICkNCg0KeCA9ICdFbmNsb3NlZFBvcmNoLmJpbi5mYWN0Jw0KeSA9ICdTYWxlUHJpY2UnDQpzdW1tYXJpemVfYnkoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KeSA9ICdsb2coU2FsZVByaWNlKScNCnN1bV9hbmRfdHJhbnNfZmFjdChkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB6ID0geSwgbWluX24gPSAzMCkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXkgPSBGDQogICkNCg0KcHJpbnQoDQogICAgcGFzdGUoDQogICAgICAiTGV2ZWxzIHcvIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IiwNCiAgICAgIHksDQogICAgICAidGhhbiBhbm90aGVyIGxldmVsOiINCiAgICApDQogICkNCnByaW50KHBfdmFscyRzaWduaWZfbGV2ZWxzKQ0KYGBgDQoNCiMjIyBDb3JyZWxhdGlvbnMNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeF9sc3QgPSBjKCdFbmNsb3NlZFBvcmNoJywgJ0VuY2xvc2VkUG9yY2guYmluJykNCnggPSAnRW5jbG9zZWRQb3JjaCcNCg0KZGYgPSBnZXRfY29ycygNCiAgZGF0YSA9IGZpbHRlcigNCiAgICBzZWxlY3QodmFsX3RyYWluX1h5LCBhbGxfb2YobnVtX2ZlYXRzKSksDQogICAgIWlzLm5hKC5kYXRhW1t4XV0pDQogICksDQogIHhfbHN0ID0geF9sc3QsDQogIGZlYXRzID0gbnVtX2ZlYXRzDQopDQpkZg0KcHJpbnQoIlN1bW1hcnkgb2YgYWJzb2x1dGUgdmFsdWVzIG9mIFBlYXJzb24ncyBSczoiKQ0KZGYgPSBhYnMoZGYpDQpzdW1tYXJ5KGFicyhkZikpDQoNCmRmID0gbWVsdChkZikNCmdncGxvdChkZiwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHZhbHVlKSkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArDQogIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMnKQ0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAuZGF0YVtbeF1dICE9IDANCiAgKSwNCiAgeF9sc3QgPSBjKHgpLA0KICBmZWF0cyA9IG51bV9mZWF0cw0KKQ0KZGYNCnByaW50KCJTdW1tYXJ5IG9mIGFic29sdXRlIHZhbHVlcyBvZiBQZWFyc29uJ3MgUnMgKG5vIDBzKToiKQ0KZGYgPSBhYnMoZGYpDQpzdW1tYXJ5KGFicyhkZikpDQoNCmRmID0gbWVsdChkZikNCmdncGxvdChkZiwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHZhbHVlKSkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArDQogIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMgKG5vIDBzKScpDQoNCnlfbHN0ID0gYygnbG9nKFNhbGVQcmljZSknKQ0KZm9yIChmZWF0IGluIHhfbHN0KSB7DQogIHBsb3Rfc2NhdF9wYWlycyhkZiA9IHZhbF90cmFpbl9YeSwgeCA9IGZlYXQsIHlfbHN0ID0geV9sc3QpDQogIHBsb3Rfc2NhdF9wYWlycygNCiAgICBkZiA9IHZhbF90cmFpbl9YeVt2YWxfdHJhaW5fWHlbW3hdXSAhPSAwLCBdLA0KICAgIHggPSBmZWF0LA0KICAgIHlfbHN0ID0geV9sc3QNCiAgKQ0KfQ0KYGBgDQoNCkhvdXNlcyB3aXRoIGVuY2xvc2VkIHBvcmNoZXMgYXJlIHNpZ25pZmljYW50bHkgY2hlYXBlciB0aGFuIHRob3NlIHdpdGhvdXQsIG1heWJlIGR1ZSB0byBhIGNvbmZvdW5kaW5nIHZhcmlhYmxlIGxpa2UgeWVhciBidWlsdC4gVGhlcmUgaXMgYSB3ZWFrLCBpZiBleGlzdGVudCwgcG9zaXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiBzcXVhcmUgZm9vdGFnZSBhbmQgcHJpY2Ugd2l0aGluIHRob3NlIHRoYXQgaGF2ZSB0aGVtLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpmZW5jZWRfamJ2KA0KICBkYXRhID0gdmFsX3RyYWluX1h5LA0KICB4ID0gJ0VuY2xvc2VkUG9yY2guYmluLmZhY3QnLA0KICB5ID0gJ2xvZyhTYWxlUHJpY2UpJywNCiAgaml0X2NvbCA9ICdZZWFyQnVpbHQnLA0KICBqaXRfYWxwaGEgPSAxDQopICsNCiAgZmFjZXRfd3JhcChmYWNldHMgPSB2YXJzKFllYXJCdWlsdC5mYWN0KSkNCg0KdmFsX3RyYWluX1h5JHJlc2lkcyA9IGxtKA0KICBgbG9nKFNhbGVQcmljZSlgIH4gYHNxcnQoQWdlKWAsDQogIHZhbF90cmFpbl9YeQ0KKSRyZXNpZHVhbHMNCg0KZ2dwbG90KHZhbF90cmFpbl9YeSwgYWVzKHggPSBFbmNsb3NlZFBvcmNoLCB5ID0gcmVzaWRzKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHNlbGVjdCgNCiAgdmFsX3RyYWluX1h5LCAtYygnRW5jbG9zZWRQb3JjaC5iaW4nLCAnRW5jbG9zZWRQb3JjaC5iaW4uZmFjdCcpDQopDQpgYGANCg0KIyMgWDNTc25Qb3JjaA0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzdW1tYXJ5KHZhbF90cmFpbl9YeSRYM1NzblBvcmNoKQ0KdmFsX3RyYWluX1h5ID0gc2VsZWN0KHZhbF90cmFpbl9YeSwgLWMoJ1gzU3NuUG9yY2gnKSkNCmBgYA0KDQojIyBTY3JlZW5Qb3JjaA0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQojIyMgTm9ybWFsaXplDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnU2NyZWVuUG9yY2gnDQpzdW1tYXJ5KHZhbF90cmFpbl9YeVt4XSkNCnN1bV9hbmRfdHJhbnNfY29udCgNCiAgZGF0YSA9IHZhbF90cmFpbl9YeVt2YWxfdHJhaW5fWHkkU2NyZWVuUG9yY2ggIT0gMCwgXSwNCiAgeCA9IHgsDQogIGZ1bmMgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJGZ1bmMsDQogIGZ1bmNfbmFtZSA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkbmFtZSwNCiAgeF9iaW53ID0gMTAsDQogIHRfYmludyA9IDAuMQ0KKQ0KYGBgDQoNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdjYnJ0KFNjcmVlblBvcmNoKScNCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKCdjYnJ0KFNjcmVlblBvcmNoKScgPSBTY3JlZW5Qb3JjaF4oMS8zKSkNCg0KIyBSZWNhbGN1bGF0ZSBiZXN0IG5vcm1hbGl6ZXJzLg0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KYmVzdF9ub3JtYWxpemVycyA9IGZpbmRfYmVzdF9ub3JtYWxpemVyX3Blcl9mZWF0KA0KICBkZiA9IHZhbF90cmFpbl9YeSwNCiAgZmVhdHNfbHN0ID0gbnVtX2ZlYXRzLA0KICBmdW5jc19sc3QgPSBmdW5jc19sc3QsDQogIGV4Y2x1ZGVfdmFscyA9IGxpc3QoMCkNCikNCg0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbeF0pDQpzdW1fYW5kX3RyYW5zX2NvbnQoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHlbdmFsX3RyYWluX1h5JGBjYnJ0KFNjcmVlblBvcmNoKWAgIT0gMCwgXSwNCiAgeCA9IHgsDQogIGZ1bmMgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJGZ1bmMsDQogIGZ1bmNfbmFtZSA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkbmFtZSwNCiAgeF9iaW53ID0gMC4xLA0KICB0X2JpbncgPSAwLjENCikNCmBgYA0KDQojIyMgV2luc29yaXplDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnY2JydChTY3JlZW5Qb3JjaCknDQpkZiA9IGZpbHRlcih2YWxfdHJhaW5fWHksIFNjcmVlblBvcmNoICE9IDApIA0KDQpxcW5vcm0oeSA9IGRmJFNjcmVlblBvcmNoLCB5bGFiID0gJ1NjcmVlblBvcmNoJykNCnFxbGluZSh5ID0gZGYkU2NyZWVuUG9yY2gpDQoNCnFxbm9ybSh5ID0gZGZbW3hdXSwgeWxhYiA9IHgpDQpxcWxpbmUoeSA9IGRmW1t4XV0pDQoNCldpbl9jYnJ0X3ggPSBXaW5zb3JpemUoDQogIGRmW1t4XV0sDQogIHByb2JzID0gYygwLjA1LCAwLjk1KSwNCiAgbmEucm0gPSBUDQopDQoNCnFxbm9ybSh5ID0gV2luX2NicnRfeCwgeWxhYiA9ICdXaW5fY2JydF94JykNCnFxbGluZSh5ID0gV2luX2NicnRfeCkNCg0KV2luX3Jhd194ID0gV2luc29yaXplKA0KICBkZiRTY3JlZW5Qb3JjaCwNCiAgcHJvYnMgPSBjKDAuMDUsIDAuOTUpLA0KICBuYS5ybSA9IFQNCikNCg0KcXFub3JtKHkgPSBXaW5fcmF3X3gsIHlsYWIgPSAnV2luX3Jhd194JykNCnFxbGluZSh5ID0gV2luX3Jhd194KQ0KDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IGRmJFNjcmVlblBvcmNoKSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gZGZbW3hdXSkpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IFdpbl9jYnJ0X3gpKQ0KcHJpbnQoc2hhcGlyby50ZXN0KHggPSBXaW5fcmF3X3gpKQ0KYGBgDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKA0KICAgICdXaW4oY2JydChTY3JlZW5Qb3JjaCkpJyA9IGlmZWxzZSgNCiAgICAgIFNjcmVlblBvcmNoID09IDAsDQogICAgICAwLA0KICAgICAgV2luc29yaXplKA0KICAgICAgICBTY3JlZW5Qb3JjaF4oMS8zKSwNCiAgICAgICAgbWludmFsID0gbWluKFdpbl9jYnJ0X3gpLA0KICAgICAgICBtYXh2YWwgPSBtYXgoV2luX2NicnRfeCkNCiAgICAgICkNCiAgICApDQogICkNCmBgYA0KDQojIyMgQmluYXJpemUNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoJ1NjcmVlblBvcmNoLmJpbicgPSBpZmVsc2UoU2NyZWVuUG9yY2ggPT0wLCAwLCAxKSkgJT4lDQogIG11dGF0ZSgnU2NyZWVuUG9yY2guYmluLmZhY3QnID0gZmFjdG9yKFNjcmVlblBvcmNoLmJpbiwgb3JkZXJlZCA9IFQpKQ0KDQp4ID0gJ1NjcmVlblBvcmNoLmJpbi5mYWN0Jw0KeSA9ICdTYWxlUHJpY2UnDQpzdW1tYXJpemVfYnkoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KeSA9ICdsb2coU2FsZVByaWNlKScNCnN1bV9hbmRfdHJhbnNfZmFjdChkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB6ID0geSwgbWluX24gPSAzMCkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXkgPSBGDQogICkNCg0KcHJpbnQoDQogICAgcGFzdGUoDQogICAgICAiTGV2ZWxzIHcvIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IiwNCiAgICAgIHksDQogICAgICAidGhhbiBhbm90aGVyIGxldmVsOiINCiAgICApDQogICkNCnByaW50KHBfdmFscyRzaWduaWZfbGV2ZWxzKQ0KYGBgDQoNCiMjIyBDb3JyZWxhdGlvbnMNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KeF9sc3QgPSBjKCdTY3JlZW5Qb3JjaCcsICdjYnJ0KFNjcmVlblBvcmNoKScsICdXaW4oY2JydChTY3JlZW5Qb3JjaCkpJywNCiAgICAgICAgICAnU2NyZWVuUG9yY2guYmluJykNCnggPSAnV2luKGNicnQoU2NyZWVuUG9yY2gpKScNCg0KZGYgPSBnZXRfY29ycygNCiAgZGF0YSA9IGZpbHRlcigNCiAgICBzZWxlY3QodmFsX3RyYWluX1h5LCBhbGxfb2YobnVtX2ZlYXRzKSksDQogICAgIWlzLm5hKC5kYXRhW1t4XV0pDQogICksDQogIHhfbHN0ID0geF9sc3QsDQogIGZlYXRzID0gbnVtX2ZlYXRzDQopDQpkZg0KcHJpbnQoIlN1bW1hcnkgb2YgYWJzb2x1dGUgdmFsdWVzIG9mIFBlYXJzb24ncyBSczoiKQ0KZGYgPSBhYnMoZGYpDQpzdW1tYXJ5KGFicyhkZikpDQoNCmRmID0gbWVsdChkZikNCmdncGxvdChkZiwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHZhbHVlKSkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArDQogIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMnKQ0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAuZGF0YVtbeF1dICE9IDANCiAgKSwNCiAgeF9sc3QgPSB4X2xzdCwNCiAgZmVhdHMgPSBudW1fZmVhdHMNCikNCmRmDQpwcmludCgiU3VtbWFyeSBvZiBhYnNvbHV0ZSB2YWx1ZXMgb2YgUGVhcnNvbidzIFJzIChubyAwcyk6IikNCmRmID0gYWJzKGRmKQ0Kc3VtbWFyeShhYnMoZGYpKQ0KDQpkZiA9IG1lbHQoZGYpDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gdmFyaWFibGUsIHkgPSB2YWx1ZSkpICsNCiAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKw0KICB5bGFiKGxhYmVsID0gJ0Fic29sdXRlIFZhbHVlIG9mIENvcnJlbGF0aW9uIHRvIE90aGVyIEZlYXR1cmVzIChubyAwcyknKQ0KDQp5X2xzdCA9IGMoJ1dpbihsb2coU2FsZVByaWNlKSknKQ0KZm9yIChmZWF0IGluIHhfbHN0KSB7DQogIHBsb3Rfc2NhdF9wYWlycyhkZiA9IHZhbF90cmFpbl9YeSwgeCA9IGZlYXQsIHlfbHN0ID0geV9sc3QpDQogIHBsb3Rfc2NhdF9wYWlycygNCiAgICBkZiA9IHZhbF90cmFpbl9YeVt2YWxfdHJhaW5fWHlbW3hdXSAhPSAwLCBdLA0KICAgIHggPSBmZWF0LA0KICAgIHlfbHN0ID0geV9sc3QNCiAgKQ0KfQ0KYGBgDQoNClRoZSBiaW5hcnkgZG9lc24ndCBzZWVtIHRvIG1ha2UgYSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGFzIG90aGVyIGJpbmFyaWVzIGxpa2UgV29vZERlY2sgYW5kIE9wZW5Qb3JjaC4gU28sIGl0J3Mgbm90IHdvcnRoIHVzaW5nIGFsb25lLCBidXQgbWlnaHQgYmUgdXNlZnVsIGluIGludGVyYWN0aW9uIHdpdGggdGhlIGFyZWEgaW4gYSBsaW5lYXIgcmVncmVzc2lvbi4gRGVjaXNpb24gdHJlZXMgc2hvdWxkIGJlIGFibGUgdG8gZG8gd2l0aG91dCB0aGUgYmluYXJ5IGZlYXR1cmUuIFRoZSB0cmFuc2Zvcm1hdGlvbiBtYXkgaGVscCBLTk4sIGJ1dCBsaWtlIGEgZGVjaXNpb24gdHJlZSwgSSB3b3VsZCB3YW50IHRvIGF2b2lkIG92ZXJ3ZWlnaHRpbmcgYnkgaW5jbHVkaW5nIGJvdGguDQoNCiMjIyAiQ29udHJvbGxpbmciDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmZlbmNlZF9qYnYoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHksDQogIHggPSAnU2NyZWVuUG9yY2guYmluLmZhY3QnLA0KICB5ID0gJ2xvZyhTYWxlUHJpY2UpJywNCiAgaml0X2NvbCA9ICdZZWFyQnVpbHQnLA0KICBqaXRfYWxwaGEgPSAxDQopICsNCiAgZmFjZXRfd3JhcChmYWNldHMgPSB2YXJzKFllYXJCdWlsdC5mYWN0KSkNCg0KdmFsX3RyYWluX1h5JHJlc2lkcyA9IGxtKA0KICBgbG9nKFNhbGVQcmljZSlgIH4gYHNxcnQoQWdlKWAsDQogIHZhbF90cmFpbl9YeQ0KKSRyZXNpZHVhbHMNCg0KZ2dwbG90KHZhbF90cmFpbl9YeSwgYWVzKHggPSBgY2JydChTY3JlZW5Qb3JjaClgLCB5ID0gcmVzaWRzKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nKQ0KDQpwcmludCgiQ29ycmVsYXRpb24gb2YgY2JydChTY3JlZW5Qb3JjaCkgdG8gcmVzaWR1YWxzIG9mIGBsb2coU2FsZVByaWNlKWAgfiBgc3FydChBZ2UpYCIpDQpwcmludChjb3IoeCA9IHZhbF90cmFpbl9YeSRgY2JydChTY3JlZW5Qb3JjaClgLCB5ID0gdmFsX3RyYWluX1h5JHJlc2lkcykpDQoNCmRmID0gZmlsdGVyKHZhbF90cmFpbl9YeSwgU2NyZWVuUG9yY2ggIT0gMCkNCg0KZ2dwbG90KGRmLCBhZXMoeCA9IGBjYnJ0KFNjcmVlblBvcmNoKWAsIHkgPSByZXNpZHMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICdsbScpDQoNCnByaW50KCJFeGx1ZGluZyBTY3JlZW5Qb3JjaCAwczoiKQ0KcHJpbnQoY29yKHggPSB2YWxfdHJhaW5fWHkkYGNicnQoU2NyZWVuUG9yY2gpYCwgeSA9IHZhbF90cmFpbl9YeSRyZXNpZHMpKQ0KYGBgDQoNClRoaXMgZmVhdHVyZSBkb2Vzbid0IHNlZW0gdG8gaGF2ZSBtdWNoIHRvIG9mZmVyLiBCdXQsIEknbGwgbGVhdmUgaXQgYW55d2F5IGFuZCBsZXQgZmVhdHVyZSBzZWxlY3Rpb24gZHVyaW5nIG1vZGVsaW5nIHN1c3MgdGhhdCBvdXQuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHNlbGVjdCgNCiAgdmFsX3RyYWluX1h5LCAtYygnU2NyZWVuUG9yY2guYmluLmZhY3QnLCAnV2luKGNicnQoU2NyZWVuUG9yY2gpKScpDQopDQpgYGANCg0KIyMgUG9vbEFyZWEsIFFDDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnN1bW1hcnkodmFsX3RyYWluX1h5JFBvb2xRQykNCnZhbF90cmFpbl9YeSA9IHNlbGVjdCh2YWxfdHJhaW5fWHksIC1jKCdQb29sQXJlYScsICdQb29sUUMnKSkNCmBgYA0KDQojIyBGZW5jZQ0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ0ZlbmNlJw0KeSA9ICdTYWxlUHJpY2UnDQpzdW1tYXJpemVfYnkoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KeSA9ICdsb2coU2FsZVByaWNlKScNCnN1bV9hbmRfdHJhbnNfZmFjdChkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB6ID0geSwgbWluX24gPSAzMCkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXkgPSBGDQogICkNCg0KcHJpbnQoDQogICAgcGFzdGUoDQogICAgICAiTGV2ZWxzIHcvIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IiwNCiAgICAgIHksDQogICAgICAidGhhbiBhbm90aGVyIGxldmVsOiINCiAgICApDQogICkNCnByaW50KHBfdmFscyRzaWduaWZfbGV2ZWxzKQ0KYGBgDQoNCiMjIyBCaW5hcml6ZQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgnRmVuY2UuYmluJyA9IGlmZWxzZShGZW5jZSA9PSAnTm9uZScsIDAsIDEpKSAlPiUNCiAgbXV0YXRlKCdGZW5jZS5iaW4uZmFjdCcgPSBmYWN0b3IoRmVuY2UuYmluLCBvcmRlcmVkID0gVCkpDQoNCnggPSAnRmVuY2UuYmluLmZhY3QnDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5LCBtaW5fbiA9IDMwKQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQpgYGANCg0KSGF2aW5nIGEgRmVuY2Ugc2VlbXMgdG8gZGV0cmFjdCB2YWx1ZSwgcHJvYmFibHkgZHVlIHRvIGFuIGludGVyYWN0aW9uIHdpdGggYW5vdGhlciB2YXJpYWJsZSBhcyB3ZSBzYXcgd2l0aCBFbmNsb3NlZFBvcmNoIGFuZCBhZ2UuIFVubGlrZSBFbmNsb3NlZFBvcmNoLCBpdCdzIG5vdCBpbW1lZGlhdGVseSBvYnZpb3VzIHdoYXQgdGhlIG90aGVyIHZhcmlhYmxlIGlzLCBhbmQgImNvbnRyb2xsaW5nIiBmb3IgaXQgd2l0aCBhIGxpbmVhciByZWdyZXNzaW9uIG1heSBhY3R1YWxseSBpbmNyZWFzZSBGZW5jZSdzIHNpZ25pZmljYW5jZS4gU28sIHJhdGhlciB0aGFuIGh1bnRpbmcgZm9yIHRoZSBvdGhlciB2YXJpYWJsZShzKSBvciBkcm9wcGluZyB0aGlzIG9uZSwgSSdsbCBsZWF2ZSBpdCBhbmQgc2VlIGlmIE1MIG1vZGVsaW5nIGNhbiBtYWtlIHVzZSBvZiBpdC4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5ID0gc2VsZWN0KA0KICB2YWxfdHJhaW5fWHksDQogIC1jKCdGZW5jZS5iaW4nLCAnRmVuY2UuYmluLmZhY3QnKQ0KKQ0KYGBgDQoNCiMjIE1pc2NGZWF0dXJlLCBNaXNjVmFsDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNCk1pc2NWYWwgaXMga2luZCBvZiBhIGNoZWF0ZXIgdmFyaWFibGUuIEl0IHNob3VsZCBoYXZlIHByZWNpc2VseSAxIGZvciBpdHMgY29lZmZpY2llbnQuIE90aGVyd2lzZSwgSSB3b3VsZCBqdXN0IGRyb3AgaXQgZm9yIHNvIGZldyBvYnNlcnZhdGlvbnM7IGl0IG1pZ2h0IGp1c3QgdGhyb3cgb2YgdGhlIHJlZ3Jlc3Npb24uIElmIEkga2VlcCBpdCwgaXQgc2hvdWxkIGJlIHRyYW5zZm9ybWVkIGluIHRoZSBzYW1lIHdheSB0aGF0IHRoZSB0YXJnZXQgdmFyaWFibGUgaXMuDQoNCkl0IGFsc28gbG9va3MgbGlrZSB0aGUgcHJlc2VuY2Ugb2YgYSBtaXNjZWxsYW5lb3VzIGltcHJvdmVtZW50IGlzIGFzc29jaWF0ZWQgd2l0aCBhIGxvd2VyIHByaWNlIGlmIGFueXRoaW5nLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ01pc2NGZWF0dXJlJw0KeSA9ICdTYWxlUHJpY2UnDQpzdW1tYXJpemVfYnkoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KeSA9ICdsb2coU2FsZVByaWNlKScNCnN1bV9hbmRfdHJhbnNfZmFjdChkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB6ID0geSwgbWluX24gPSAzMCkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXkgPSBGDQogICkNCg0KcHJpbnQoDQogICAgcGFzdGUoDQogICAgICAiTGV2ZWxzIHcvIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IiwNCiAgICAgIHksDQogICAgICAidGhhbiBhbm90aGVyIGxldmVsOiINCiAgICApDQogICkNCnByaW50KHBfdmFscyRzaWduaWZfbGV2ZWxzKQ0KYGBgDQoNCiMjIE1pc2NWYWwgDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNCiMjIyBOb3JtYWxpemUNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdNaXNjVmFsJw0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbeF0pDQpzdW1fYW5kX3RyYW5zX2NvbnQoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHlbdmFsX3RyYWluX1h5JE1pc2NWYWwgIT0gMCwgXSwNCiAgeCA9IHgsDQogIGZ1bmMgPSBiZXN0X25vcm1hbGl6ZXJzW1t4XV0kYmVzdF9mdW5jJGZ1bmMsDQogIGZ1bmNfbmFtZSA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkbmFtZSwNCiAgeF9iaW53ID0gMjAwLA0KICB0X2JpbncgPSAuMQ0KKQ0KYGBgDQoNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdsb2cyKE1pc2NWYWwpJw0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoDQogICAgJ2xvZzIoTWlzY1ZhbCknID0gaWZlbHNlKA0KICAgICAgTWlzY1ZhbCA9PSAwLA0KICAgICAgMCwNCiAgICAgIGxvZzIoTWlzY1ZhbCkNCiAgICApDQogICkNCg0KIyBSZWNhbGN1bGF0ZSBiZXN0IG5vcm1hbGl6ZXJzLg0KbnVtX2ZlYXRzID0gY29sbmFtZXMoc2VsZWN0KHZhbF90cmFpbl9YeSwgd2hlcmUoaXMubnVtZXJpYykpKQ0KYmVzdF9ub3JtYWxpemVycyA9IGZpbmRfYmVzdF9ub3JtYWxpemVyX3Blcl9mZWF0KA0KICBkZiA9IHZhbF90cmFpbl9YeSwNCiAgZmVhdHNfbHN0ID0gbnVtX2ZlYXRzLA0KICBmdW5jc19sc3QgPSBmdW5jc19sc3QsDQogIGV4Y2x1ZGVfdmFscyA9IGxpc3QoMCkNCikNCg0Kc3VtbWFyeSh2YWxfdHJhaW5fWHlbeF0pDQpzdW1fYW5kX3RyYW5zX2NvbnQoDQogIGRhdGEgPSB2YWxfdHJhaW5fWHlbdmFsX3RyYWluX1h5JGBsb2cyKE1pc2NWYWwpYCAhPSAwLCBdLA0KICB4ID0geCwNCiAgZnVuYyA9IGJlc3Rfbm9ybWFsaXplcnNbW3hdXSRiZXN0X2Z1bmMkZnVuYywNCiAgZnVuY19uYW1lID0gYmVzdF9ub3JtYWxpemVyc1tbeF1dJGJlc3RfZnVuYyRuYW1lLA0KICB4X2JpbncgPSAuMSwNCiAgdF9iaW53ID0gLjENCikNCmBgYA0KDQpBbHJpZ2h0LCBJJ2xsIGdpdmUgaXQgYSBuYXR1cmFsIGxvZyBsaWtlIFNhbGVQcmljZSwgc2luY2UgaXQgaXMgYSBzdHJhaWdodCBkb2xsYXIgdmFsdWUuDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnggPSAnbG9nKE1pc2NWYWwpJw0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoDQogICAgJ2xvZyhNaXNjVmFsKScgPSBpZmVsc2UoDQogICAgICBNaXNjVmFsID09IDAsDQogICAgICAwLA0KICAgICAgbG9nKE1pc2NWYWwpDQogICAgKQ0KICApDQoNCmRmID0gZmlsdGVyKHZhbF90cmFpbl9YeSwgTWlzY1ZhbCAhPSAwKQ0KDQpnZyA9IGdncGxvdChkZiwgYWVzKHggPSBgbG9nKE1pc2NWYWwpYCkpDQpwMSA9IGdnICsgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjEpDQpwMiA9IGdnICsgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkNCmdyaWQuYXJyYW5nZShwMSwgcDIpDQpgYGANCg0KIyMjIFdpbnNvcml6ZQ0KDQpTaW5jZSB0aGlzIHZhcmlhYmxlIGlzIGFuIGFjdHVhbCBkb2xsYXIgdmFsdWUsIFdpbnNvcml6aW5nIGl0IGRvZXNuJ3QgcmVhbGx5IG1ha2Ugc2Vuc2UuIEknbGwgY2hlY2sgaXQgb3V0IGFueXdheS4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcXFub3JtKHkgPSBkZiRNaXNjVmFsLCB5bGFiID0gJ01pc2NWYWwnKQ0KcXFsaW5lKHkgPSBkZiRNaXNjVmFsKQ0KDQpxcW5vcm0oeSA9IGRmJGBsb2coTWlzY1ZhbClgLCB5bGFiID0gJ2xvZyhNaXNjVmFsKScpDQpxcWxpbmUoeSA9IGRmJGBsb2coTWlzY1ZhbClgKQ0KDQpXaW5fbG9nX3ggPSBXaW5zb3JpemUoDQogIGRmJGBsb2coTWlzY1ZhbClgLA0KICBwcm9icyA9IGMoMC4wMDcsIDAuOTUpLA0KICBuYS5ybSA9IFQNCikNCg0KcXFub3JtKHkgPSBXaW5fbG9nX3gsIHlsYWIgPSAnV2luX2xvZ194JykNCnFxbGluZSh5ID0gV2luX2xvZ194KQ0KDQpXaW5fcmF3X3ggPSBXaW5zb3JpemUoDQogIGRmJE1pc2NWYWwsDQogIHByb2JzID0gYygwLCAwLjk1KQ0KKQ0KDQpxcW5vcm0oeSA9IFdpbl9yYXdfeCwgeWxhYiA9ICdXaW5fcmF3X3gnKQ0KcXFsaW5lKHkgPSBXaW5fcmF3X3gpDQoNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gZGYkTWlzY1ZhbCkpDQpwcmludChzaGFwaXJvLnRlc3QoeCA9IGRmJGBsb2coTWlzY1ZhbClgKSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gV2luX2xvZ194KSkNCnByaW50KHNoYXBpcm8udGVzdCh4ID0gV2luX3Jhd194KSkNCmBgYA0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp2YWxfdHJhaW5fWHkgPSB2YWxfdHJhaW5fWHkgJT4lDQogIG11dGF0ZSgNCiAgICAnV2luKGxvZyhNaXNjVmFsKSknID0gaWZlbHNlKA0KICAgICAgTWlzY1ZhbCA9PSAwLA0KICAgICAgMCwNCiAgICAgIFdpbnNvcml6ZSgNCiAgICAgICAgbG9nKE1pc2NWYWwpLA0KICAgICAgICBtaW52YWwgPSBtaW4oV2luX2xvZ194KSwNCiAgICAgICAgbWF4dmFsID0gbWF4KFdpbl9sb2dfeCkNCiAgICAgICkNCiAgICApDQogICkNCmBgYA0KDQojIyMgQmluYXJpemUNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoJ01pc2NWYWwuYmluJyA9IGlmZWxzZShNaXNjVmFsID09IDAsIDAsIDEpKSAlPiUNCiAgbXV0YXRlKCdNaXNjVmFsLmJpbi5mYWN0JyA9IGZhY3RvcihNaXNjVmFsLmJpbiwgb3JkZXJlZCA9IFQpKQ0KDQp4ID0gJ01pc2NWYWwuYmluLmZhY3QnDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5LCBtaW5fbiA9IDMwKQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQpgYGANCg0KIyMjIENvcnJlbGF0aW9ucw0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpudW1fZmVhdHMgPSBjb2xuYW1lcyhzZWxlY3QodmFsX3RyYWluX1h5LCB3aGVyZShpcy5udW1lcmljKSkpDQp4X2xzdCA9IGMoJ01pc2NWYWwnLCAnbG9nMihNaXNjVmFsKScsICdsb2coTWlzY1ZhbCknLA0KICAgICAgICAgICdXaW4obG9nKE1pc2NWYWwpKScsICdNaXNjVmFsLmJpbicpDQoNCnggPSAnbG9nKE1pc2NWYWwpJw0KDQpkZiA9IGdldF9jb3JzKA0KICBkYXRhID0gZmlsdGVyKA0KICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiAgICAhaXMubmEoLmRhdGFbW3hdXSkNCiAgKSwNCiAgeF9sc3QgPSB4X2xzdCwNCiAgZmVhdHMgPSBudW1fZmVhdHMNCikNCmRmDQpwcmludCgiU3VtbWFyeSBvZiBhYnNvbHV0ZSB2YWx1ZXMgb2YgUGVhcnNvbidzIFJzOiIpDQpkZiA9IGFicyhkZikNCnN1bW1hcnkoYWJzKGRmKSkNCg0KZGYgPSBtZWx0KGRmKQ0KZ2dwbG90KGRmLCBhZXMoeCA9IHZhcmlhYmxlLCB5ID0gdmFsdWUpKSArDQogIGdlb21fYm94cGxvdChub3RjaCA9IFQpICsNCiAgeWxhYihsYWJlbCA9ICdBYnNvbHV0ZSBWYWx1ZSBvZiBDb3JyZWxhdGlvbiB0byBPdGhlciBGZWF0dXJlcycpDQoNCmRmID0gZ2V0X2NvcnMoDQogIGRhdGEgPSBmaWx0ZXIoDQogICAgc2VsZWN0KHZhbF90cmFpbl9YeSwgYWxsX29mKG51bV9mZWF0cykpLA0KICAgIC5kYXRhW1t4XV0gIT0gMA0KICApLA0KICB4X2xzdCA9IHhfbHN0LA0KICBmZWF0cyA9IG51bV9mZWF0cw0KKQ0KZGYNCnByaW50KCJTdW1tYXJ5IG9mIGFic29sdXRlIHZhbHVlcyBvZiBQZWFyc29uJ3MgUnMgKG5vIDBzKToiKQ0KZGYgPSBhYnMoZGYpDQpzdW1tYXJ5KGFicyhkZikpDQoNCmRmID0gbWVsdChkZikNCmdncGxvdChkZiwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHZhbHVlKSkgKw0KICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArDQogIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMgKG5vIDBzKScpDQoNCnlfbHN0ID0gYygnV2luKGxvZyhTYWxlUHJpY2UpKScpDQpmb3IgKGZlYXQgaW4geF9sc3QpIHsNCiAgcGxvdF9zY2F0X3BhaXJzKGRmID0gdmFsX3RyYWluX1h5LCB4ID0gZmVhdCwgeV9sc3QgPSB5X2xzdCkNCiAgcGxvdF9zY2F0X3BhaXJzKA0KICAgIGRmID0gdmFsX3RyYWluX1h5W3ZhbF90cmFpbl9YeVtbeF1dICE9IDAsIF0sDQogICAgeCA9IGZlYXQsDQogICAgeV9sc3QgPSB5X2xzdA0KICApDQp9DQpgYGANCg0KIyMjICJDb250cm9sbGluZyINCg0KTWlzY1ZhbCBhcHBlYXJzIHRvIGJlIGNvcnJlbGF0ZWQgd2l0aCB0aGUgc2l6ZSBvZiB0aGUgbG90IGFuZCBob3VzZS4gUGVyaGFwcyB0aGF0IHdpbGwgZG8gdGhlIGhlYXZ5IGxpZnRpbmcgYW5kIG1ha2UgTWlzY1ZhbCBvYnNvbGV0ZS4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5JHJlc2lkcyA9IGxtKA0KICBgbG9nKFNhbGVQcmljZSlgIH4gYFdpbihMb3RBcmVhKWAsDQogIHZhbF90cmFpbl9YeQ0KKSRyZXNpZHVhbHMNCg0KcHJpbnQoIkNvcnJlbGF0aW9uIG9mIGxvZyhNaXNjVmFsKSB0byByZXNpZHVhbHMgb2YgYGxvZyhTYWxlUHJpY2UpYCB+IGBXaW4oTG90QXJlYSlgIikNCnByaW50KGNvcih4ID0gdmFsX3RyYWluX1h5JGBsb2coTWlzY1ZhbClgLCB5ID0gdmFsX3RyYWluX1h5JHJlc2lkcykpDQoNCmdncGxvdCh2YWxfdHJhaW5fWHksIGFlcyh4ID0gYGxvZyhNaXNjVmFsKWAsIHkgPSByZXNpZHMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICdsbScpICsNCiAgeWxhYihsYWJlbCA9ICJgbG9nKFNhbGVQcmljZSlgIH4gYFdpbihMb3RBcmVhKWAiKQ0KDQpkZiA9IGZpbHRlcih2YWxfdHJhaW5fWHksIE1pc2NWYWwgIT0gMCkNCg0KcHJpbnQoIkV4bHVkaW5nIE1pc2NWYWwgMHM6IikNCnByaW50KGNvcih4ID0gZGYkYGxvZyhNaXNjVmFsKWAsIHkgPSBkZiRyZXNpZHMpKQ0KDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gYGxvZyhNaXNjVmFsKWAsIHkgPSByZXNpZHMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICdsbScpICsNCiAgeWxhYihsYWJlbCA9ICJgbG9nKFNhbGVQcmljZSlgIH4gYFdpbihMb3RBcmVhKWAiKQ0KDQoNCnZhbF90cmFpbl9YeSRyZXNpZHMgPSBsbSgNCiAgYGxvZyhTYWxlUHJpY2UpYCB+IGBsb2cxMChsb2cxMChMb3RBcmVhKSlgLA0KICB2YWxfdHJhaW5fWHkNCikkcmVzaWR1YWxzDQoNCnByaW50KCJDb3JyZWxhdGlvbiBvZiBsb2coTWlzY1ZhbCkgdG8gcmVzaWR1YWxzIG9mIGBsb2coU2FsZVByaWNlKWAgfiBgbG9nMTAobG9nMTAoTG90QXJlYSkpYCIpDQpwcmludChjb3IoeCA9IHZhbF90cmFpbl9YeSRgbG9nKE1pc2NWYWwpYCwgeSA9IHZhbF90cmFpbl9YeSRyZXNpZHMpKQ0KDQpnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IGBsb2coTWlzY1ZhbClgLCB5ID0gcmVzaWRzKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nKSArDQogIHlsYWIobGFiZWwgPSAiYGxvZyhTYWxlUHJpY2UpYCB+IGBsb2cxMChsb2cxMChMb3RBcmVhKSlgIikNCg0KZGYgPSBmaWx0ZXIodmFsX3RyYWluX1h5LCBNaXNjVmFsICE9IDApDQoNCnByaW50KCJFeGx1ZGluZyBNaXNjVmFsIDBzOiIpDQpwcmludChjb3IoeCA9IGRmJGBsb2coTWlzY1ZhbClgLCB5ID0gZGYkcmVzaWRzKSkNCg0KZ2dwbG90KGRmLCBhZXMoeCA9IGBsb2coTWlzY1ZhbClgLCB5ID0gcmVzaWRzKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nKSArDQogIHlsYWIobGFiZWwgPSAiYGxvZyhTYWxlUHJpY2UpYCB+IGBsb2cxMChsb2cxMChMb3RBcmVhKSlgIikNCmBgYA0KDQpBZnRlciBmYWN0b3JpbmcgaW4gTG90IEFyZWEsIHRoZXJlJ3Mgc3RpbGwgc29tZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIE1pc2NWYWwgYW5kIHRoZSB0YXJnZXQgb25jZSAwcyBhcmUgcmVtb3ZlZC4gSXQgbWF5IHN0aWxsIGJlIHdvcnRoIGtlZXBpbmcuIFdlJ2xsIGxldCBtb2RlbGluZyBzdXNzIHRoYXQgb3V0Lg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp2YWxfdHJhaW5fWHkgPSBzZWxlY3QoDQogIHZhbF90cmFpbl9YeSwgLWMoJ01pc2NWYWwuYmluJywgJ01pc2NWYWwuYmluLmZhY3QnLCAnbG9nMihNaXNjVmFsKScpDQogICkNCmBgYA0KDQojIyBNb1NvbGQsIFlyU29sZA0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpNb1NvbGQgc29tZXdoYXQgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgYXJvdW5kIEp1bmUvSnVseS4gQ29udmVudGlvbmFsIHdpc2RvbSBzYXlzIHRoYXQgc3VtbWVyIHNhbGVzIGFyZSBoaWdoZXIgaW4gdm9sdW1lIGFuZCBwcmljZSwgYnV0IHRoZSBkYXRhIGRvbid0IGJlYXIgdGhhdCBvdXQgZm9yIHByaWNlLg0KDQpSZWNvcmRzIGdvIGZyb20gSmFudWFyeSAyMDA2IHRocm91Z2ggSnVseSAyMDEwLCBzbyBBdWd1c3QtRGVjZW1iZXIgYXJlIHVuZGVycmVwcmVzZW50ZWQgYnkgcm91Z2hseSAyMCUuDQoNCllyU29sZCBwcmV0dHkgdW5pZm9ybSAoYWJvdXQgMTQwLTE2MCkgZXhjZXB0IGZvciAyMDEwIHdoaWNoIGVuZGVkIGluIEp1bHkgaW4gdGhpcyBzZXQuDQoNCkludGVyZXN0aW5nIHNwaWtlIGluIHNhbGVzIGluIFNwcmluZyAyMDEwLiBGb3JlY2xvc3VyZXMgY29taW5nIG9udG8gbWFya2V0Pw0KDQojIyMgU2V0IHRvIEZhY3RvcnM6IE1vU29sZCwgWXJTb2xkDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnZhbF90cmFpbl9YeSA9IHZhbF90cmFpbl9YeSAlPiUNCiAgbXV0YXRlKCdNb1NvbGQuZmFjdCcgPSBmYWN0b3IoTW9Tb2xkKSkgJT4lDQogIG11dGF0ZSgnWXJTb2xkLmZhY3QnID0gZmFjdG9yKFlyU29sZCwgb3JkZXJlZCA9IFQpKQ0KDQp4ID0gJ01vU29sZC5mYWN0Jw0KeSA9ICdTYWxlUHJpY2UnDQpzdW1tYXJpemVfYnkoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KeSA9ICdsb2coU2FsZVByaWNlKScNCnN1bV9hbmRfdHJhbnNfZmFjdChkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB6ID0geSwgbWluX24gPSAzMCkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXkgPSBGDQogICkNCg0KcHJpbnQoDQogICAgcGFzdGUoDQogICAgICAiTGV2ZWxzIHcvIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IiwNCiAgICAgIHksDQogICAgICAidGhhbiBhbm90aGVyIGxldmVsOiINCiAgICApDQogICkNCnByaW50KHBfdmFscyRzaWduaWZfbGV2ZWxzKQ0KDQoNCnggPSAnWXJTb2xkLmZhY3QnDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KcF92YWxzID0gZ2V0X3NpZ25pZl9sZXZlbHMoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHogPSB5LCBtaW5fbiA9IDMwKQ0KDQpoZWF0bWFwLjIoDQogICAgeCA9IGFzLm1hdHJpeChwX3ZhbHMkcHZhbF9kZiksDQogICAgc2NhbGUgPSAnbm9uZScsDQogICAgUm93diA9IEYsDQogICAgQ29sdiA9IEYsDQogICAgZGVuZHJvZ3JhbSA9ICdub25lJywNCiAgICBjZWxsbm90ZSA9IGZvcm1hdChwX3ZhbHMkcHZhbF9kZiwgZGlnaXRzID0gMiksDQogICAgbm90ZWNleCA9IDAuNzUsDQogICAgbm90ZWNvbCA9ICdibGFjaycsDQogICAgbWFpbiA9IHBhc3RlKHksICdwLXZhbHVlcycpLA0KICAgIGtleSA9IEYNCiAgKQ0KDQpwcmludCgNCiAgICBwYXN0ZSgNCiAgICAgICJMZXZlbHMgdy8gc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQiLA0KICAgICAgeSwNCiAgICAgICJ0aGFuIGFub3RoZXIgbGV2ZWw6Ig0KICAgICkNCiAgKQ0KcHJpbnQocF92YWxzJHNpZ25pZl9sZXZlbHMpDQpgYGANCg0KIyMgU29sZERhdGUNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmFsX3RyYWluX1h5ID0gdmFsX3RyYWluX1h5ICU+JQ0KICBtdXRhdGUoDQogICAgU29sZERhdGUgPSBhcy5EYXRlKA0KICAgICAgcGFzdGUoDQogICAgICAgIGFzLmNoYXJhY3RlcihZclNvbGQpLA0KICAgICAgICBhcy5jaGFyYWN0ZXIoTW9Tb2xkKSwNCiAgICAgICAgJzE1JywNCiAgICAgICAgc2VwID0gJy8nDQogICAgICApLA0KICAgICAgZm9ybWF0ID0gJyVZLyVtLyVkJw0KICAgICkNCiAgKQ0KDQpnZ3Bsb3QodmFsX3RyYWluX1h5LCBhZXMoeCA9IFNvbGREYXRlKSkgKw0KICBnZW9tX2JhcigpDQoNCnggPSAnU29sZERhdGUnDQp5ID0gJ1NhbGVQcmljZScNCnN1bW1hcml6ZV9ieShkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQp5ID0gJ2xvZyhTYWxlUHJpY2UpJw0Kc3VtX2FuZF90cmFuc19mYWN0KGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB5ID0geSkNCg0KDQpudW1fZmVhdHMgPSBjb2xuYW1lcyhzZWxlY3QodmFsX3RyYWluX1h5LCB3aGVyZShpcy5udW1lcmljKSkpDQp4X2xzdCA9IGMoJ1NvbGREYXRlJykNCiMgDQojIGRmID0gZ2V0X2NvcnMoDQojICAgZGF0YSA9IGZpbHRlcigNCiMgICAgIHNlbGVjdCh2YWxfdHJhaW5fWHksIGFsbF9vZihudW1fZmVhdHMpKSwNCiMgICAgICFpcy5uYSguZGF0YVtbeF1dKQ0KIyAgICksDQojICAgeF9sc3QgPSB4X2xzdCwNCiMgICBmZWF0cyA9IG51bV9mZWF0cw0KIyApDQojIGRmDQojIHByaW50KCJTdW1tYXJ5IG9mIGFic29sdXRlIHZhbHVlcyBvZiBQZWFyc29uJ3MgUnM6IikNCiMgZGYgPSBhYnMoZGYpDQojIHN1bW1hcnkoYWJzKGRmKSkNCiMgDQojIGRmID0gbWVsdChkZikNCiMgZ2dwbG90KGRmLCBhZXMoeCA9IHZhcmlhYmxlLCB5ID0gdmFsdWUpKSArDQojICAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKw0KIyAgIHlsYWIobGFiZWwgPSAnQWJzb2x1dGUgVmFsdWUgb2YgQ29ycmVsYXRpb24gdG8gT3RoZXIgRmVhdHVyZXMnKQ0KDQp5X2xzdCA9IGMoJ2xvZyhTYWxlUHJpY2UpJykNCnBsb3Rfc2NhdF9wYWlycyhkZiA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHlfbHN0ID0geV9sc3QpDQpgYGANCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dwbG90KA0KICB2YWxfdHJhaW5fWHksDQogIGFlcyh4ID0gU29sZERhdGUsIHkgPSBgbG9nKFNhbGVQcmljZSlgKQ0KKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nLCBjb2xvciA9ICdZZWxsb3cnKSArDQogIGZhY2V0X2dyaWQocm93cyA9IHZhcnMoU2FsZUNvbmRpdGlvbiksIGNvbHMgPSB2YXJzKFNhbGVUeXBlKSkNCmBgYA0KDQpOb25lIG9mIGRhdGUgc2VlbXMgdG8gbWF0dGVyIGluIHRoaXMgc2V0LiBEcm9wIGl0LCBidXQgbm8geWV0Lg0KDQojIyBTYWxlVHlwZQ0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp4ID0gJ1NhbGVUeXBlJw0KeSA9ICdTYWxlUHJpY2UnDQpzdW1tYXJpemVfYnkoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KeSA9ICdsb2coU2FsZVByaWNlKScNCnN1bV9hbmRfdHJhbnNfZmFjdChkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB6ID0geSwgbWluX24gPSAzMCkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXkgPSBGDQogICkNCg0KcHJpbnQoDQogICAgcGFzdGUoDQogICAgICAiTGV2ZWxzIHcvIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IiwNCiAgICAgIHksDQogICAgICAidGhhbiBhbm90aGVyIGxldmVsOiINCiAgICApDQogICkNCnByaW50KHBfdmFscyRzaWduaWZfbGV2ZWxzKQ0KYGBgDQoNCjxhIGlkPSJzYWxldHlwZURhdGUiPjwvYT4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGYgPSB2YWxfdHJhaW5fWHlbdmFsX3RyYWluX1h5JFNhbGVUeXBlICVpbiUgYygnV0QnLCAnTmV3JywgJ0NPRCcpLCBdDQpnZyA9IGdncGxvdChkZiwgYWVzKHggPSBTYWxlVHlwZSkpDQoNCmdnICsgZ2VvbV9iYXIoKSArDQogIGZhY2V0X2dyaWQocm93cyA9IHZhcnMoTW9Tb2xkKSwgY29scyA9IHZhcnMoWXJTb2xkKSkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQ0KDQpnZyArIGdlb21fYmFyKHBvc2l0aW9uID0gJ2RvZGdlJywgYWVzKGZpbGwgPSBmYWN0b3IoWXJTb2xkKSkpDQoNCmdncGxvdChkZiwgYWVzKGNvbG9yID0gU2FsZVR5cGUsIHggPSBTb2xkRGF0ZSkpICsNCiAgZ2VvbV9mcmVxcG9seSgpDQoNCiMgZ2dwbG90KGRmLCBhZXMoeCA9IFNhbGVUeXBlLCB5ID0gU2FsZVByaWNlKSkgKw0KIyAgIGdlb21fY29sKHBvc2l0aW9uID0gJ2RvZGdlJywgYWVzKGZpbGwgPSBmYWN0b3IoWXJTb2xkKSwgc3RhdCA9ICdtZWFuJykpDQoNCmdncGxvdChkZiwgYWVzKHggPSBTYWxlVHlwZSwgeSA9IGBsb2coU2FsZVByaWNlKWApKSArDQogIGdlb21fYm94cGxvdChwb3NpdGlvbiA9ICdkb2RnZScsIG5vdGNoID0gVCwgYWVzKGNvbG9yID0gZmFjdG9yKFlyU29sZCkpKQ0KYGBgDQoNCllvdSBjYW4gc2VlIG5ldyBob21lIHNhbGVzIGRyb3Agb2ZmIHdpdGggdGhlIGNyYXNoLiBDb3VydCBvZmZpY2VyIGRlZWRzIGFsc28gdGlja2VkIHVwLCBwb3NzaWJseSBkdWUgdG8gbW9yZSBmb3JlY2xvc3VyZXMuIEJ1dCwgdGhlcmUgZGlkbid0IHNlZW0gdG8gYmUgYSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGluIHNhbGUgcHJpY2VzIHllYXIgb3ZlciB5ZWFyIHdpdGhpbiBzYWxlIHR5cGUgZ3JvdXBzLCBleGNlcHQgYmV0d2VlbiBuZXcgc2FsZXMgaW4gMjAwN2FuZCAyMDEwIHdoaWNoIGlzIG5vdCBmdWxseSByZXByZXNlbnRlZCBpbiB0aGlzIHNldC4NCg0KIyMgU2FsZUNvbmRpdGlvbg0KDQpbQmFjayB0byB0b3AuXSgjdG9wKQ0KDQo1OTIgbm9ybWFsLCA2MSBwYXJ0aWFsIChob21lIG5vdCBjb21wbGV0ZWQgd2hlbiBsYXN0IGFzc2Vzc2VkKSwgNDMgYWJub3JtYWwgKHRyYWRlLCBmb3JlY2xvc3VyZSwgc2hvcnQgc2FsZSksIDExIGZhbWlseS4gSSdtIGd1ZXNzaW5nIGEgZmFtaWx5IHNhbGUgd2lsbCBiZSBsb3dlciBpbiBwcmljZSB0eXBpY2FsbHksIGFzIHdpbGwgZm9yZWNsb3N1cmVzIGFuZCBzaG9ydHNhbGVzLiBJJ20gbm90IHN1cmUgd2hhdCB0byBtYWtlIG9mIHBhcnRpYWxzOyB0aGUgaG91c2Ugd2Fzbid0IGZ1bGx5IGJ1aWx0IHdoZW4gYXNzZXNzZWQsIHNvIHRoZSBwcmljZSBtYXkgYmUgYXNrZXc/DQoNClN1cnByaXNpbmdseSwgYWJub3JtYWwgc2FsZXMgZGlkbid0IHNlZW0gdG8gdmFyeSB3aXRoIHRoZSBjcmFzaC4gQW1lcyBhcHBlYXJzIHRvIGhhdmUgZmFyZWQgd2VsbC4NCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeCA9ICdTYWxlQ29uZGl0aW9uJw0KeSA9ICdTYWxlUHJpY2UnDQpzdW1tYXJpemVfYnkoZGF0YSA9IHZhbF90cmFpbl9YeSwgeCA9IHgsIHkgPSB5KQ0KeSA9ICdsb2coU2FsZVByaWNlKScNCnN1bV9hbmRfdHJhbnNfZmFjdChkYXRhID0gdmFsX3RyYWluX1h5LCB4ID0geCwgeSA9IHkpDQoNCnBfdmFscyA9IGdldF9zaWduaWZfbGV2ZWxzKGRhdGEgPSB2YWxfdHJhaW5fWHksIHggPSB4LCB6ID0geSwgbWluX24gPSAzMCkNCg0KaGVhdG1hcC4yKA0KICAgIHggPSBhcy5tYXRyaXgocF92YWxzJHB2YWxfZGYpLA0KICAgIHNjYWxlID0gJ25vbmUnLA0KICAgIFJvd3YgPSBGLA0KICAgIENvbHYgPSBGLA0KICAgIGRlbmRyb2dyYW0gPSAnbm9uZScsDQogICAgY2VsbG5vdGUgPSBmb3JtYXQocF92YWxzJHB2YWxfZGYsIGRpZ2l0cyA9IDIpLA0KICAgIG5vdGVjZXggPSAwLjc1LA0KICAgIG5vdGVjb2wgPSAnYmxhY2snLA0KICAgIG1haW4gPSBwYXN0ZSh5LCAncC12YWx1ZXMnKSwNCiAgICBrZXkgPSBGDQogICkNCg0KcHJpbnQoDQogICAgcGFzdGUoDQogICAgICAiTGV2ZWxzIHcvIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IiwNCiAgICAgIHksDQogICAgICAidGhhbiBhbm90aGVyIGxldmVsOiINCiAgICApDQogICkNCnByaW50KHBfdmFscyRzaWduaWZfbGV2ZWxzKQ0KYGBgDQoNCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGYgPSBmaWx0ZXIodmFsX3RyYWluX1h5LCBTYWxlQ29uZGl0aW9uICVpbiUgYygnTm9ybWFsJywgJ0Fibm9ybWwnLCAnUGFydGlhbCcpKQ0KDQpnZyA9IGdncGxvdChkZiwgYWVzKHggPSBTYWxlQ29uZGl0aW9uKSkNCg0KZ2cgKyBnZW9tX2JhcigpICsNCiAgZmFjZXRfZ3JpZChyb3dzID0gdmFycyhNb1NvbGQpLCBjb2xzID0gdmFycyhZclNvbGQpKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpDQoNCmdnICsgZ2VvbV9iYXIoYWVzKGZpbGwgPSBmYWN0b3IoWXJTb2xkKSksIHBvc2l0aW9uID0gJ2RvZGdlJykNCg0KZ2dwbG90KGRmLCBhZXMoeCA9IFNvbGREYXRlLCBjb2xvciA9IFNhbGVDb25kaXRpb24pKSArDQogIGdlb21fZnJlcXBvbHkoKQ0KDQpnZ3Bsb3QoZGYsIGFlcyh4ID0gU2FsZUNvbmRpdGlvbiwgeSA9IGBsb2coU2FsZVByaWNlKWApKSArDQogIGdlb21fYm94cGxvdChwb3NpdGlvbiA9ICdkb2RnZScsIG5vdGNoID0gVCwgYWVzKGNvbG9yID0gZmFjdG9yKFlyU29sZCkpKQ0KYGBgDQoNCkJlYXJpbmcgaW4gbWluZCB0aGF0IDIwMTAgaXMgbm90IGEgY29tcGxldGUgeWVhciBpbiB0aGlzIHNldCwgcGFydGlhbCBzYWxlcyBkcm9wcGVkIGFzIG5vcm1hbCBzYWxlcyBpbmNyZWFzZWQuIFRoaXMgdHJlbmQgbWF5IGJlIGV4cGxhaW5lZCBieSBkZXZlbG9wZXJzIGZpbmlzaGluZy9oYWx0aW5nIHRoZWlyIHByb2plY3RzLiBGaWx0ZXJpbmcvZ3JvdXBpbmcgYnkgeWVhciBidWlsdCBhbmQvb3IgbmVpZ2hib3Job29kIG1pZ2h0IGhlbHAgY2hlY2sgdGhpcywgYnV0IEknbGwgc2tpcCBpdCBmb3IgdGhlIHNha2Ugb2YgZmluaXNoaW5nLg0KDQpGYWxsIGFuZCB3aW50ZXIgbW9udGhzIHNlZW1lZCB0byBiZSB3aGVyZSB0aGUgYnVsayBvZiB0aGVzZSBpbmNyZWFzZXMgaW4gbm9ybWFsIHNhbGVzIGZlbGwgZWFjaCB5ZWFyLg0KDQoNCiMjIE92ZXJhbGwgQ29ycmVsYXRpb25zDQoNCltCYWNrIHRvIHRvcC5dKCN0b3ApDQoNClRvIHdyYXAgdXAgdGhpcyBub3RlYm9vaywgYW5kIGFzIGEgcHJlbGltaW5hcnkgZ2F1Z2Ugb24gaG93IHdlbGwgSSBoYXZlIHByZXBhcmVkIHRoZSBkYXRhIGZvciBNTCwgbWFpbmx5IGZvciBsaW5lYXIgcmVncmVzc2lvbiwgSSdsbCBjaGVjayBvdXQgaG93IGNvcnJlbGF0aW9ucyBoYXZlIGNoYW5nZWQuIEhhdmUgbXkgdmFyaWFibGVzIGluY3JlYXNlZCBpbiBjb3JyZWxhdGlvbiBpbiBnZW5lcmFsPyBIYXZlIHRoZXkgaW5jcmVhc2VkIGluIGNvcnJlbGF0aW9uIHRvIHRoZSB0YXJnZXQgdmFyaWFibGU/DQoNCkluY3JlYXNlZCBjb3JyZWxhdGlvbnMgdG8gdGhlIHRhcmdldCB2YXJpYWJsZSBoYXZlIG9idmlvdXMgYmVuZWZpdHMuIEluY3JlYXNlZCBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwcmVkaWN0b3IgdmFyaWFibGVzIHdpbGwgaGVscCBjbGFyaWZ5IHdoaWNoIHZhcmlhYmxlcyBtYXkgb3ZlcmxhcCBpbiB0aGVpciBwcmVkaWN0aXZlIHBvd2VyIGFuZCByZWR1bmRhbnRseSBvdmVyd2VpZ2h0IHRoZSBzYW1lIHVuZGVybHlpbmcgaW5mb3JtYXRpb24uDQoNCkkgbWFkZSBhIGxvdCBvZiBuZXcgZmVhdHVyZXMsIGRyb3BwaW5nIHNvbWUgYWxvbmcgdGhlIHdheS4gSW4gY2FzZXMgd2hlcmUgYSBXaW5zb3JpemVkIHZlcnNpb24gc2VlbWVkIGEgZ29vZCBvcHRpb24sIEkgYWxzbyBrZXB0IHRoZSBzY2FsZS10cmFuc2Zvcm1lZCB2ZXJzaW9uIHdoZXJlIGFwcGxpY2FibGUuIEZvciBleGFtcGxlLCBJIGtlcHQgYm90aCBsb2coU2FsZVByaWNlKSBhbmQgV2luKGxvZyhTYWxlUHJpY2UpKS4gV2luc29yaXphdGlvbiB3aWxsIGhlbHAgYSBzdHJhaWdodGZvcndhcmQgbGluZWFyIHJlZ3Jlc3Npb24gd2l0aG91dCBpbnRlcmFjdGlvbnMuIEJ1dCwgV2luc29yaXphdGlvbiB3aWxsIHVuZGVyY3V0IEtOTidzIGFiaWxpdHkgdG8gY2x1c3RlciBtdWx0aXZhcmlhdGUgb3V0bGllcnMgYW5kIHNpbWlsYXJseSBSRidzIGFiaWxpdHkgdG8gZ3JvdXAgYnkgZXh0cmVtZXMuIFRoYXQgc2FpZCwgV2luc29yaXphdGlvbiB3aWxsIHJlZHVjZSB0aGUgY2hhbmNlcyBvZiBvdmVyZml0IGluIGFsbCB0aHJlZS4gQWxsIHRoYXQgaXMgdG8gc2F5IHRoYXQgdGhlcmUgYXJlIHJlZHVuZGFudCBuZXcgZmVhdHVyZXMuDQoNCkkgY2FuJ3QgdGhpbmsgb2YgYSBxdWljayBhbmQgZGlydHkgd2F5IHRvIHZpZXcgdGhpcyB3aXRob3V0IGdldHRpbmcgc2tld2VkIHJlc3VsdHMgb3IgYnVzeSBoZWF0bWFwcywgb3RoZXIgdGhhbiB0byBtYWtlIGEgdGFibGUgb2YgYWxsIHRoZSB2YXJpYWJsZXMnIGNvcnJlbGF0aW9ucyB0byB0aGUgdGFyZ2V0IHZhcmlhYmxlIHdoaWNoIGlzbid0IHZlcnkgZWFzaWx5IGRpZ2VzdGVkIGVpdGhlci4gVGhlIHNsb3cgYW5kIHRlZGlvdXMgd2F5IHdvdWxkIGJlIHRvIGl0ZXJhdGl2ZWx5ICJtYW51YWxseSIgKHRvIHNvbWUgZXh0ZW50KSBzZWxlY3QgbGlrZSB2YXJpYWJsZXMgZm9yIGNvcnJlbGF0aW9uIHdpdGhpbiB0aGVpciBleHBlcmltZW50YWwgZ3JvdXAuIEknbSBub3QgZ29pbiB0byBkbyB0aGF0Lg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp2YWxfdHJhaW5fWHlfbnVtZXJpYyA9IHNlbGVjdCgNCiAgdmFsX3RyYWluX1h5LCAjIFJlb3JkZXIgZm9yIGVhc2llciBjb21wYXJpc29uLg0KICBjKCdTYWxlUHJpY2UnLCAnbG9nKFNhbGVQcmljZSknLCAnV2luKGxvZyhTYWxlUHJpY2UpKScsICJMb3RGcm9udGFnZSIsDQogICAgImxvZzEwKExvdEZyb250YWdlKSIsICJXaW4obG9nMTAoTG90RnJvbnRhZ2UpKSIsICJXaW4oTG90RnJvbnRhZ2UpIiwNCiAgICAiTG90QXJlYSIsICJsb2cxMChsb2cxMChMb3RBcmVhKSkiLCAiV2luKExvdEFyZWEpIiwgIk92ZXJhbGxRdWFsX2ludCIsDQogICAgIk92ZXJhbGxDb25kX2ludCIsICJZZWFyQnVpbHQiLCAic3FydChBZ2UpIiwgIlllYXJSZW1vZEFkZCIsICJNYXNWbnJBcmVhIiwNCiAgICAiY2JydChNYXNWbnJBcmVhKSIsICJXaW4oY2JydChNYXNWbnJBcmVhKSkiLCAiQnNtdEZpblNGMSIsICJCc210RmluU0YyIiwNCiAgICAiQnNtdFVuZlNGIiwgImNicnQoQnNtdFVuZlNGKSIsICJzcXVhcmUobG9nKFRvdGFsQnNtdFNGKSkiLA0KICAgICJXaW4oc3F1YXJlKGxvZyhUb3RhbEJzbXRTRikpKSIsICJUb3RhbEJzbXRTRiIsICJUb3RhbEJzbXRGaW5TRiIsDQogICAgInNxcnQoVG90YWxCc210RmluU0YpIiwgIldpbihzcXJ0KFRvdGFsQnNtdEZpblNGKSkiLCAiWDJuZEZsclNGIiwNCiAgICAiWDJuZEZsci5iaW4iLCAiR3JMaXZBcmVhIiwgInNxdWFyZShsb2cyKEdyTGl2QXJlYSkpIiwgDQogICAgIldpbihzcXVhcmUobG9nMihHckxpdkFyZWEpKSkiLCAiVG90QmF0aHMiLCAiV2luKFRvdEJhdGhzKSIsDQogICAgIkJlZHJvb21BYnZHciIsICJXaW4oQmVkcm9vbUFidkdyKSIsICJUb3RSbXNBYnZHcmQiLCAiV2luKEJlZHJvb21BYnZHcikiLA0KICAgICJUb3RSbXNBYnZHcmQiLCAiV2luKFRvdFJtc0FidkdyZCkiLCAiRmlyZXBsYWNlcy5iaW4iLCAiR2FyYWdlQ2FycyIsDQogICAgIldpbihHYXJhZ2VDYXJzKSIsICJXb29kRGVja1NGIiwgImNicnQoV29vZERlY2tTRikiLA0KICAgICJXaW4oY2JydChXb29kRGVja1NGKSkiLCAiT3BlblBvcmNoU0YiLCAiT3BlblBvcmNoLmJpbiIsICJFbmNsb3NlZFBvcmNoIiwNCiAgICAiU2NyZWVuUG9yY2giLCAiY2JydChTY3JlZW5Qb3JjaCkiLCAiU2NyZWVuUG9yY2guYmluIiwgIk1pc2NWYWwiLA0KICAgICJsb2coTWlzY1ZhbCkiLCAiV2luKGxvZyhNaXNjVmFsKSkiLCAiTW9Tb2xkIiwgIllyU29sZCIpDQopDQoNCmdnY29ycih2YWxfdHJhaW5fWHlfbnVtZXJpYykNCg0KY29yX210eCA9IGNvcih2YWxfdHJhaW5fWHlfbnVtZXJpYywgdXNlID0gJ3BhaXJ3aXNlLmNvbXBsZXRlLm9icycpDQoNCnRhcmdldF92YXJzX3ZlYyA9IGMoJ1NhbGVQcmljZScsICdsb2coU2FsZVByaWNlKScsICdXaW4obG9nKFNhbGVQcmljZSkpJykNCg0KY29yX210eF9tZWx0ZWQgPSBtZWx0KGNvcl9tdHgpDQpzYWxlc19jb3JfbXR4X21lbHRlZCA9IGZpbHRlcigNCiAgY29yX210eF9tZWx0ZWQsDQogIFZhcjEgJWluJSB0YXJnZXRfdmFyc192ZWMgJiAhKFZhcjIgJWluJSB0YXJnZXRfdmFyc192ZWMpDQopDQoNCmdncGxvdChzYWxlc19jb3JfbXR4X21lbHRlZCwgYWVzKHggPSBWYXIxLCB5ID0gVmFyMikpICsNCiAgZ2VvbV90aWxlKGFlcyhmaWxsID0gdmFsdWUpKQ0KDQpkY2FzdChzYWxlc19jb3JfbXR4X21lbHRlZCwgZm9ybXVsYSA9IFZhcjIgfiBWYXIxKQ0KDQpmZW5jZWRfamJ2KA0KICBkYXRhID0gc2FsZXNfY29yX210eF9tZWx0ZWQsDQogIHggPSAnVmFyMScsDQogIHkgPSAndmFsdWUnLA0KICBqaXRfaCA9IDANCikNCmBgYA0KDQojIFNlcmlhbGl6ZSBEYXRhZnJhbWUgZm9yIFN0b3JhZ2UNCg0KW0JhY2sgdG8gdG9wLl0oI3RvcCkNCg0KSSdsbCB3cml0ZSBpdCB0byBhIENTViBmaWxlIHNvIEkgY2FuIHZlcmlmeSB0aGF0IG15IGZpbmFsIGVuZ2luZWVyaW5nIHNjcmlwdCBkdXBsaWNhdGVzIHRoaXMgcHJvY2Vzcy4gSSdsbCB2ZXJpZnkgaW4gdGhlIG5leHQgbm90ZWJvb2sgYmVmb3JlIEkgc3RhcnQgbW9kZWxpbmcuDQoNCmBgYHtyIGVjaG89VFJVRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCnZhbF90cmFpbl9YeSRJZCA9IHZhbF90cmFpbl9YJElkDQoNCnNhdmVSRFModmFsX3RyYWluX1h5LCAnZGF0YS9lZGFfdmFsX3RyYWluX1h5LnJkcycpDQpoZWFkKHJlYWRSRFMoJ2RhdGEvZWRhX3ZhbF90cmFpbl9YeS5yZHMnKSkNCmBgYA==